Reverse Proxy for Homelab

Once a homelab grows past one or two services, you stop wanting to remember port numbers and you start wanting clean URLs with HTTPS. A reverse proxy provides exactly that — a single entry point that fronts every internal service, terminates TLS, applies authentication, and routes by hostname. It is the architectural piece that turns "Plex on 32400 and Jellyfin on 8096 and Home Assistant on 8123" into "plex.example.com, jellyfin.example.com, home.example.com, all on standard ports with valid HTTPS."

What the proxy does

  1. Listens on standard ports (80 and 443).
  2. Inspects the incoming Host header or TLS SNI to identify which backend service the request is for.
  3. Optionally authenticates the request.
  4. Forwards to the backend service over the internal network.
  5. Returns the backend's response to the client.

The client sees a single hostname and port. The backend services don't need to be reachable directly; they can listen on arbitrary internal ports.

Why one entry point

  • One port to expose. Port forwarding (if exposing externally) needs just port 443 → proxy. Without the proxy, every service needs its own port forward.
  • One certificate. A wildcard TLS cert covers all subdomains. Without the proxy, every service needs its own cert.
  • One auth layer. Authelia, Authentik, or even basic auth sit at the proxy and protect services that don't have their own auth.
  • Clean URLs. Users access services by hostname instead of memorizing port numbers.
  • Hidden internal structure. Backend services can move between hosts; users see the same URL.

Common reverse-proxy options for homelab

ToolStrengthBest for
Nginx Proxy Manager (NPM)Web UI on top of nginx with built-in Let's EncryptBeginners; click-driven config
CaddyAutomatic HTTPS; declarative configSimple deployments; minimal config
TraefikAuto-discovers services from Docker labelsDocker-heavy environments
nginx (raw)Maximum control and performanceAdvanced users; complex routing
HAProxyHigh-performance TCP and HTTP proxyHeavy load; layer-4 needs

Hostname-based routing

The most common routing model. Each service gets its own subdomain that resolves to the proxy. The proxy looks at the request's Host header and forwards accordingly:

plex.example.com    → 10.0.0.42:32400
jellyfin.example.com → 10.0.0.43:8096
home.example.com    → 10.0.0.44:8123
sonarr.example.com  → 10.0.0.45:8989

Requires DNS configuration:

  • External: each subdomain has an A record pointing at your public IP.
  • Internal (split-horizon): each subdomain has a private A record pointing at the proxy's internal IP, so devices inside the LAN don't NAT-hairpin through the router.

For internal DNS, run Pi-hole, AdGuard Home, or your router's DNS service. See DNS for homelab.

Path-based routing

Alternative: all services on one hostname, different paths:

home.example.com/plex     → Plex
home.example.com/jellyfin → Jellyfin
home.example.com/sonarr   → Sonarr

Simpler DNS but every backend service must support being served from a subpath (some don't). Hostname-based is more common.

TLS termination

The proxy holds the TLS certificate and decrypts incoming HTTPS. Backend services receive plain HTTP from the proxy over the trusted internal network.

Certificate options:

  • Let's Encrypt — free, automated, browser-trusted. Use DNS-01 challenge for wildcard certificates that cover all subdomains. See Let's Encrypt for self-hosted.
  • Self-signed — works but produces browser warnings. Useful only for purely-internal services accessed by one or two people who can install a custom CA.
  • Internal CA — set up your own CA, distribute the root cert to all client devices. Works for fully-internal setups but operational overhead.

Authentication patterns

For services that lack their own auth (or have weak auth), proxy-level authentication adds a layer:

  • Basic auth — simplest. nginx auth_basic with htpasswd file. Crude but works.
  • Authelia / Authentik — full SSO solution. Single login screen; the user authenticates once and the proxy passes the identity to all backend services.
  • OAuth proxy — delegate auth to Google, GitHub, etc. via OAuth2 Proxy.
  • mTLS — client certificates. Strong; UX is rough for non-technical users.

WebSockets and long-lived connections

Modern services (Home Assistant, real-time dashboards, chat) use WebSockets — long-lived TCP connections with HTTP upgrade. Reverse proxies need explicit configuration to handle them:

  • nginx: proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";
  • Caddy and Traefik typically handle WebSockets correctly by default.
  • Long idle timeouts may need adjustment so the proxy doesn't kill idle WebSocket connections.

External access patterns

Three common ways to expose the proxy externally:

  • Port forward 443 from router to proxy. Simplest. Requires a static public IP or dynamic DNS, and exposes the proxy to the internet directly.
  • Cloudflare Tunnel. Cloudflare runs an outbound tunnel from your proxy; users reach Cloudflare which forwards to your tunnel. No port forwarding; works behind CGNAT; Cloudflare sees decrypted traffic.
  • VPN-only access. Don't expose externally at all; users connect via WireGuard or Tailscale and reach the proxy on the internal IP. Most secure; requires VPN setup per user.

Common pitfalls

  • Forgetting WebSocket headers. Services with realtime features break in subtle ways.
  • Backend services not respecting X-Forwarded-* headers. Services see the proxy's IP, not the user's IP. Set trust proxies appropriately.
  • Hostname mismatch with TLS cert. Cert covers example.com but services use *.example.com — request hits wrong-cert error. Use wildcard cert.
  • Hairpin NAT not configured. Internal devices accessing the external hostname get routed through the router which can't hairpin. Use split-horizon DNS for internal access.
  • Authentication bypassing. Backend service is also exposed directly (forgot to firewall off the internal port), so attackers skip the proxy auth.

Frequently Asked Questions

What is a reverse proxy?

A server that accepts incoming requests and forwards them to backend services based on configuration. The client talks only to the proxy; the proxy talks to the actual service. Used in homelabs to expose many services through one port with one TLS certificate, applying hostname-based or path-based routing rules.

Why use a reverse proxy in a homelab?

Three reasons. First, one port (typically 443) on the public side, regardless of how many services run inside. Second, one TLS certificate management point (often Let's Encrypt) instead of per-service. Third, central authentication enforcement — services that don't have their own auth can be protected by the proxy.

Which reverse proxy should I use?

For homelabs, the popular choices are Nginx Proxy Manager (web UI on top of nginx, easiest), Traefik (Docker-native, auto-configures from container labels), Caddy (automatic HTTPS, simple config), and bare nginx (most flexible but manual). Beginners typically start with NPM or Caddy; Docker-heavy setups gravitate to Traefik; advanced users may use raw nginx.

What is hostname-based routing?

The proxy decides where to forward each request based on the hostname in the request (the Host header for HTTP, SNI for HTTPS). plex.example.com goes to the Plex VM; jellyfin.example.com goes to the Jellyfin container; both arrive on the same port 443 of the proxy. Requires DNS for each subdomain to point at the proxy.

Does the reverse proxy need to be public?

No. Many homelab setups use a reverse proxy only on the internal network — services have nice internal hostnames (plex.lan, jellyfin.lan) routed by an internal proxy, but nothing is exposed to the internet. For services that should be reachable externally, a separate public-facing proxy or a VPN / Cloudflare Tunnel is the typical entry point.

Related Guides

More From This Section