0–5 min: classify exposure
List every hostname, port, tunnel, CDN, proxy, and intended audience.
Success checkNo public route exists without a named need and owner.
Self-hosted public exposure checklist · Updated 2026-07-05
A practical public-exposure checklist for self-hosted apps behind NGINX, Caddy, tunnels, VPNs, and home-server gateways.
Putting a reverse proxy in front of a self-hosted app does not automatically make it safe for the internet. The real exposure chain includes DNS, TLS, proxy trust, client-IP handling, authentication, admin surface separation, WebSocket upgrades, upload limits, request and connection limits, logs, fail-open behavior, and the ability to shut off access quickly.
This checklist applies to CasaOS, Umbrel, Nextcloud, YunoHost, RomM, Penpot, and similar self-hosted systems. It treats LAN-only, VPN-only, authenticated public access, and fully public services as different operating modes with different controls.
RepoDaily verdict
Default to LAN-only or VPN-only access unless public exposure is a real product requirement. When public exposure is necessary, terminate TLS at a controlled edge, define trusted proxies explicitly, keep admin surfaces separate, preserve the real client IP without trusting arbitrary forwarding headers, test WebSockets and large uploads deliberately, apply rate and connection limits where appropriate, and keep a tested emergency shutoff path.
| Surface | Baseline rule | Failure signal | Evidence |
|---|---|---|---|
| DNS | Point only intended names at public ingress | Wildcard or stale records expose forgotten services | DNS inventory and owner |
| TLS | Use valid certificates and automated renewal with alerts | Expired cert or fallback plaintext path | Renewal test and expiry monitor |
| Trusted proxies | Trust only known proxy addresses/ranges | App trusts arbitrary X-Forwarded-* headers | Proxy chain map and negative spoof test |
| Client IP | Preserve real client identity through reviewed proxy chain | All users appear as proxy IP or attackers spoof source IP | Access-log sample and spoofing test |
| Authentication | Require real auth before privileged access | Internet reachability equals authorization | Anonymous and wrong-role tests |
| Admin surface | Keep admin routes private or strongly separated | Same public entry point exposes root/admin console | Route inventory and access test |
| WebSockets | Proxy upgrade explicitly and authenticate session | HTTP works but socket bypasses controls | Handshake/origin/auth tests |
| Uploads | Set intentional size and timeout limits | Unlimited uploads exhaust disk or legitimate files fail unexpectedly | Boundary-size tests |
| Rate limits | Apply endpoint-appropriate request and connection controls | Login/API endpoints can be hammered without bound | Load and abuse test |
| Logs | Record auth, proxy, upstream, and security events with redaction | No client identity or logs leak tokens | Sample incident trace |
| Shutoff | Have a tested way to disable public ingress | Only recovery is editing production live | Runbook and exercise result |
Score the exposure path from internet to application, not only the reverse proxy config.
| Control | 0 points | 1 point | 2 points | Owner question |
|---|---|---|---|---|
| Exposure mode | Everything public | Some routes private | LAN/VPN/public modes explicitly classified | Which services truly need internet access? |
| TLS lifecycle | Manual or unclear | Auto renewal only | Auto renewal + expiry monitoring + failure alert | Who notices renewal failure? |
| Proxy trust | Trust all headers | Single proxy configured | Explicit chain with trusted ranges and spoof tests | Which hop is allowed to assert client identity? |
| Authentication | App default only | Strong app auth | Strong auth + admin separation + session controls | Can public users ever reach admin flows? |
| Protocol handling | HTTP only tested | WebSockets/uploads tested manually | Automated HTTP/WS/upload boundary tests | Do non-HTTP paths preserve the same policy? |
| Abuse controls | None | Basic rate limit | Endpoint-aware rate, connection, body-size and timeout controls | Which endpoint is cheapest to abuse? |
| Observability | Proxy logs only | App + proxy logs | Correlated client/proxy/upstream/security events with redaction | Can one abusive session be reconstructed? |
| Emergency response | Manual config edits | Documented disable step | Tested ingress shutoff, DNS/tunnel revocation and rollback path | How fast can public access be stopped? |
Use this before opening a self-hosted service to the internet.
List every hostname, port, tunnel, CDN, proxy, and intended audience.
Success checkNo public route exists without a named need and owner.
Record TLS termination and trusted proxy addresses; try spoofed forwarded headers from an untrusted client.
Success checkClient identity cannot be forged through direct requests.
Try anonymous, normal-user, wrong-role, and direct-origin access paths.
Success checkPublic users cannot reach admin surfaces or bypass the proxy path.
Exercise WebSocket upgrade, representative upload, oversize upload, slow request, and burst requests.
Success checkLegitimate flows work and abuse boundaries fail predictably.
Trace one login failure and one successful session across proxy and app logs; inspect redaction.
Success checkReal client identity and events are traceable without exposing secrets.
Disable the public route while keeping private operator access, then restore it deliberately.
Success checkEmergency response does not depend on improvising production edits.
| Scenario | Recommended exposure mode | Required controls | Stop condition |
|---|---|---|---|
| CasaOS admin dashboard | LAN or VPN only | Private routing, strong admin auth, no direct public port | Dashboard is reachable anonymously or from arbitrary internet clients |
| Umbrel home cloud | VPN-first; selective public apps only | Per-app route review, TLS, auth, update ownership | Whole appliance is exposed because one app needs sharing |
| Nextcloud | Authenticated public service if needed | Trusted proxies, TLS, brute-force protection, upload/WebDAV tests, logs | Forwarded IP trust is broad or admin/login abuse cannot be traced |
| YunoHost multi-app server | Per-domain/per-app policy | TLS, app auth, admin separation, firewall and app-specific review | One weak app compromises trust assumptions for every domain |
| RomM sharing | Authenticated public access or VPN | User roles, upload limits, WebSocket test if used, storage monitoring | Friend-sharing endpoint grants broader library/admin access |
| Penpot team collaboration | Authenticated public or VPN-only | SSO/auth review, WebSocket support, upload limits, private admin ops | Realtime collaboration bypasses proxy controls |
| Temporary demo | Short-lived authenticated public route | Expiry, narrow DNS, no production secrets, auto shutoff | Demo remains online after review window |
| Emergency incident | Public ingress off, operator path private | Preserve logs, revoke tunnel/DNS route if needed, rotate secrets, patch privately | Team reopens before root cause and credential scope are known |
A proxy can terminate TLS and centralize policy, but weak app authentication, broad admin routes, or trusted-header mistakes remain dangerous.
If an app trusts X-Forwarded-* headers from arbitrary clients, attackers may spoof client identity used in logs, redirects, or abuse controls.
Home-server dashboards, container managers, backup consoles, and root admin panels should not become public merely because app traffic uses the same proxy.
HTTP requests may be protected while WebSocket upgrades fail, bypass expected middleware, or remain open longer than intended.
Unlimited body size, long timeouts, and public upload endpoints can consume disk, memory, temp space, or upstream workers.
One generic limit can break sync clients and uploads while still leaving sensitive login or expensive API endpoints insufficiently protected.
Access logs, debug traces, query strings, headers, and upstream errors can expose tokens, share links, filenames, or personal data.
During compromise, teams often need to stop public ingress while retaining private operator access and preserving evidence.
Keep host dashboards and admin interfaces on LAN or VPN. Publicly route only named services with reviewed requirements.
Document which proxy can set client identity headers, and configure downstream trusted-proxy lists accordingly.
Use separate hostnames, routes, VPN policy, or identity-aware access so public user traffic cannot reach administrative surfaces.
Maintain simple automated tests for HTTP redirects, login, WebSocket upgrade, representative upload size, and real-client-IP logging.
Apply stricter limits to login, password reset, expensive search, upload, and API endpoints than to static assets or normal browsing.
Keep a documented and rehearsed way to disable a hostname, route, tunnel, or public listener while private operator access remains available.
Short answers for self-hosted operators.
No. HTTPS protects transport. You still need authentication, proxy trust, admin separation, update discipline, abuse controls, logs, backups, and incident response.
Usually keep host and platform administration on LAN or VPN. Expose only the application routes that genuinely need public access.
Applications often rely on forwarded headers for client IP, host, and scheme. Only known proxies should be allowed to assert those values.
Yes. Proxy upgrade handling, authentication, origin expectations, idle timeouts, and connection limits can differ from ordinary HTTP requests.
No. Login, password reset, expensive search, API, and upload endpoints have different cost and abuse characteristics.
A tested way to disable public ingress quickly while preserving private operator access, logs, and recovery capability.
Feedback
Anonymous feedback helps RepoDaily improve what is actually useful.