Files
hermes-config/skills/self-hosting/docker-service-deployment/references/selfhosted-service-audit.md
T
2026-07-12 10:17:17 -04:00

6.3 KiB

Self-Hosted Service Security Audit

A comprehensive methodology for assessing the security posture of any self-hosted web service (KasmVNC, Immich, Paperless, Mealie, etc.) behind a VPS reverse proxy.

Audit Checklist (Run in Order)

1. Network Exposure

# DNS resolution — where does the domain point?
dig +short <subdomain>.<domain> @1.1.1.1

# Compare against server's public IP
curl -s ifconfig.me

# If they differ → VPS reverse proxy in front → good
# If they match → service directly exposed → verify UFW

2. TLS / Transport Security

# Full handshake info + headers
curl -sI -v https://<domain>/ 2>&1 | grep -E '(SSL|TLS|certificate|HTTP/|Server|Strict-Transport|X-Content|X-Frame|CSP|Referrer)'

# Certificate details
echo | openssl s_client -servername <domain> -connect <domain>:443 2>/dev/null | openssl x509 -noout -text | grep -E '(Subject:|Issuer:|Not Before|Not After|Subject Alternative)'

Check for:

  • TLS 1.2+ only (no TLS 1.0/1.1, no SSLv3)
  • Strong ciphers (AES-GCM, ChaCha20) — not RC4, 3DES, or MD5
  • Let's Encrypt or similar trusted CA (not self-signed)
  • HSTS header present (Strict-Transport-Security)
  • Certificate covers the domain (Subject Alternative Name)

3. HTTP Security Headers

curl -sI https://<domain>/ | grep -i -E '(strict-transport-security|x-content-type-options|x-frame-options|content-security-policy|referrer-policy|permissions-policy)'

Check for:

  • Strict-Transport-Security — HSTS, ideally max-age>=31536000
  • X-Content-Type-Options: nosniff — prevents MIME sniffing
  • X-Frame-Options: DENY or SAMEORIGIN — clickjacking protection
  • Content-Security-Policy — XSS mitigation (can be tricky with SPAs/WebSockets)
  • Referrer-Policy — controls referrer leakage
  • Missing headers are a hardening opportunity, not always critical for internal services

4. Nginx / Reverse Proxy Config

# Inspect the nginx site config
cat /etc/nginx/sites-available/<service>

# Check for server_name — does it match the subdomain?
# Check for auth_basic — any nginx-level auth?
grep -rn "auth_basic" /etc/nginx/ 2>/dev/null

# Check for rate limiting
grep -rn "limit_req" /etc/nginx/ 2>/dev/null

# Check for proxy_hide_header — which headers are stripped?
grep "proxy_hide_header" /etc/nginx/sites-available/<service>

Check for:

  • server_name includes ALL valid domains (DuckDNS + custom domain)
  • auth_basic for defense-in-depth (recommended for single-user services)
  • limit_req to prevent brute force
  • proxy_hide_header — strips unwanted backend headers (critical for KasmVNC COEP/COOP)
  • proxy_ssl_verify off — acceptable for localhost backends, note it's not verifying

5. Docker Container Security

# List containers with status
docker ps --format "table {{.Names}}\t{{.Status}}"

# Deep inspect
docker inspect <container> --format '{{.HostConfig.NetworkMode}}'          # network mode
docker inspect <container> --format '{{.HostConfig.CapDrop}}'              # dropped capabilities
docker inspect <container> --format '{{.HostConfig.CapAdd}}'               # added capabilities
docker inspect <container> --format '{{.HostConfig.SecurityOpt}}'          # security opts
docker inspect <container> --format '{{.Config.User}}'                     # running user

Check for:

  • Network mode: bridge (isolated, preferred) vs host (full host network access — riskier)
  • Capabilities: ideally --cap-drop=ALL with explicit --cap-add for what's needed
  • Running user: non-root inside container (e.g., kasm-user is good)
  • Security opts: no-new-privileges:true is a hardening bonus
  • Restart policy: unless-stopped (good) vs always (also fine)

6. Container Internals

# Inside the container
docker exec <container> whoami
docker exec <container> cat /etc/os-release 2>/dev/null
docker exec <container> cat /etc/debian_version 2>/dev/null
docker exec <container> uname -r 2>/dev/null

# Application version
docker exec <container> <app> --version 2>/dev/null

Check for:

  • Base OS age (Ubuntu 20.04 is past standard EOL in April 2025)
  • Application version recency
  • Image build date (docker inspect <image> --format '{{.Created}}')
  • Whether the image is regularly updated

7. Firewall and Host Defense

# UFW rules
sudo ufw status verbose

# iptables rules for the service port
sudo iptables -L INPUT -n --line-numbers | grep <port>

# Security monitoring
dpkg -l fail2ban crowdsec rkhunter chkrootkit 2>/dev/null | grep '^ii'

Check for:

  • Default inbound policy: DROP (not ACCEPT)
  • Service port allowed only from needed ranges (LAN, Tailscale VPN)
  • Public internet blocked at the firewall level
  • fail2ban or similar running for brute-force protection

8. VPS Reverse Proxy Architecture

# Trace the architecture
dig +short <subdomain>.<domain>          # → VPS IP
# VPS proxies to home server via Tailscale
ssh ubuntu@<vps-ip> 'grep proxy_pass /etc/nginx/sites-enabled/<service>'

Check for:

  • DNS points to VPS, not home server IP
  • VPS nginx proxies via Tailscale IP (100.x.x.x), not public IP
  • UFW on home server allows port only from LAN + Tailscale ranges
  • Home router has NO port forward for this service (zero open ports)

9. Risk Summary Matrix

Layer Weakness Severity Mitigation
Network Public exposure via DNS Low if behind VPS UFW, VPS proxy
Transport Missing HSTS Low add_header in nginx
Auth Single auth layer Medium Add nginx auth_basic
Container Host networking High Switch to bridge + iptables
Container Default capabilities Medium --cap-drop=ALL + explicit adds
Container Old base OS Medium Regular image pulls
App No rate limiting Low limit_req in nginx

Interpretation Guide

  • Low severity = hardening opportunity, not urgent. Benefits of fixing may not justify risk of breaking the service.
  • Medium severity = address when convenient. Adds defense-in-depth.
  • High severity = actively exploitable if attacker gains any toehold. Prioritize.

The firewall + VPS architecture is almost always the strongest defense. Even a poorly-configured container behind a proper firewall is hard to reach from the internet. Focus hardening effort where it reduces blast radius once an attacker is inside the network.