Skip to content
Bytearium

No ads or affiliate links active

Networking

Reverse proxy for a homelab

A reverse proxy accepts connections, terminates TLS and forwards by hostname to internal services; it is an entry point, not a replacement for identity and policy.

A homelab collects web interfaces the way a junk drawer collects cables – and nobody remembers which port opens which one. A reverse proxy gives every service a proper name and puts them all behind one front door.

The short answer

A reverse proxy is a single service that accepts all web requests, reads which name the client asked for, and hands each request to the right service behind it. It also ends the encryption in one place, so you manage certificates once instead of once per service. It makes a good front door, but it is not a complete security concept on its own.

How it works

  1. Reverse versus forward. A forward proxy works on behalf of clients: the client knows about it and, for example, uses the CONNECT method to reach outside sites. A reverse proxy works on behalf of servers: clients are not aware of it and simply talk to what looks like the service [5]. In a homelab you almost always want the reverse kind – one address, many services behind it. For this guide we assume that names like dienst.homelab.example already resolve to the proxy; how that works is the topic of our article on DNS name resolution.
  2. TLS ends at the proxy, the name picks the route. When a client opens a connection, TLS itself has no way to say which server it wants. The SNI extension fills that gap: RFC 6066 describes it for servers that host several virtual servers behind one network address, and says a server may use it to choose the certificate [13]. Inside the encrypted connection, the HTTP Host header carries the name again, and the proxy uses it to pick the backend. Caddy's documentation describes both values as what servers use for routing and certificate selection [1]. After termination, Caddy by default talks plain HTTP to the backend [1]. NIST SP 800-207 holds that network location alone does not imply trust and that communication should be secured regardless of location [14]. So decide per service whether the last hop needs TLS as well, and remember that an https:// upstream may need a matching Host value [1].
  3. Certificates for internal names, option one: a public CA with DNS-01. ACME (RFC 8555) is the protocol that automates proving control of a name and issuing the certificate [11]. Let's Encrypt offers HTTP-01, which needs port 80 reachable from the internet and cannot issue wildcards, and DNS-01, which proves control through a TXT record, supports wildcards and works for servers that are not exposed [9]. Public CAs only issue for names you actually control: Caddy lists localhost, .local, .internal and .home.arpa among the names that do not qualify for publicly trusted certificates [2]. So internal services need names under a domain you own. The trade-offs: DNS-01 needs API credentials for your DNS provider, and Let's Encrypt warns that full credentials on a web server raise the damage if it is compromised; it suggests narrowly scoped credentials, a separate validation server, or delegating _acme-challenge via CNAME [9]. Issued certificates are logged publicly, and Let's Encrypt points to Certificate Transparency search tools for this [10]. In our reading, a wildcard certificate keeps individual service names out of the certificate itself. Traefik's certificate resolver documents the same DNS challenge, including CNAME delegation [8]. Be careful with rate limits: as of 18 Oct 2026, according to Let's Encrypt (page last updated 5 Aug 2026), 5 certificates per identical set of names and 50 per registered domain every 7 days, with a staging environment that has higher limits [10]. Caddy also advises testing against staging [2].
  4. Option two: your own CA. Caddy can generate a local CA with a root and an intermediate; leaf certificates are signed by the intermediate. This does not use ACME or DNS validation, and it is trusted only where the root is installed [2]. By Caddy's documented defaults, the root lasts 10 years, the intermediate 7 days and leaf certificates 12 hours [3]. Caddy tries to install the root into the trust store of its own machine, but that can fail in containers or under an unprivileged user; then you run caddy trust or copy the root out by hand [3]. Putting the root on other devices is your job as administrator [2]. Every client without the root shows security errors [2]. And because a trusted root can vouch for any name on every device that trusts it, protect its key. Caddy calls trusting it safe as long as your computer is not compromised and the key does not leak [2].
  5. Forwarded headers and WebSockets. Behind a proxy, the backend sees the proxy as the client. RFC 7239 describes this loss of information and defines the Forwarded header with the parameters by, for, host and proto; it calls the header optional and sensitive and says it should be off by default [12]. The older X-Forwarded-For, X-Forwarded-Proto and X-Forwarded-Host are the de-facto, non-standard variants [12]. Caddy sets all three by default and ignores incoming values unless they come from addresses you list as trusted_proxies [1]. Traefik has forwardedHeaders.trustedIPs per entry point and documents its insecure mode as for tests only [7]. In nginx, the original Host is not passed by default; the proxied server gets $proxy_host unless you set proxy_set_header Host $host; [6]. WebSockets need extra care: Upgrade and Connection are hop-by-hop headers, so nginx needs proxy_set_header Upgrade $http_upgrade; and a matching Connection header, and it closes idle tunnels after 60 seconds by default [5]. Caddy supports WebSockets itself but closes them on a config reload unless you set stream_close_delay [1].
  6. Typical failure patterns. Redirect loops often appear when the backend sees plain HTTP, believes the visitor is insecure and redirects to HTTPS again and again. Check that the proxy sends the original scheme and host and that the app is told to trust the proxy. Logs showing only the proxy address as client point to missing or untrusted forwarded headers. Certificate warnings mostly come from one of three causes: the CA root is not trusted on that client, the name in the browser is not in the certificate, or someone opened the service by IP address. RFC 6066 notes that a mismatch between requested name and certificate name shows up when the client checks the server identity [13].
  7. Security: one entry point means one target. Funnelling everything through one proxy simplifies logging, certificates and updates. It also means that this proxy is the first thing an attacker meets, so keep its software current. Forward only the ports the proxy needs, and keep management interfaces such as NAS or hypervisor consoles out of public reach. Bind backends so they accept traffic only from the proxy, otherwise the proxy can be bypassed – that is our recommendation, not a quote from a source. A pre-check can add login in front of an app: Caddy's forward_auth sends a copy of the request to an authentication gateway, continues on a 2xx answer and otherwise passes the gateway's response, usually a login redirect, back to the client [4]. Treat logs as sensitive data; RFC 7239 itself calls the client details in forwarded headers sensitive [12].
  8. The Zero Trust connection, stated carefully. In NIST SP 800-207, a policy enforcement point enables, monitors and eventually terminates connections between a subject and a resource, and it can be a gateway component in front of the resource [14]. A reverse proxy can play that part. The same document expects an organisation to have identity, credential and access management plus asset management in place, and describes access as decided per session by dynamic policy that includes the state of identity and device [14]. A reverse proxy alone brings none of that. In our reading, it is a possible enforcement point, not a replacement for identities, an inventory or written policies.

A homelab example

In our homelab at Bytearium, an internal certificate authority exists, based on "Caddy Local Authority 2026 ECC Root". The certificate is prepared on one workstation but not yet entered as system-wide trusted (status 14–18 Oct 2026). Until that step is done, clients relying on the system trust store would show warnings for certificates from that CA, because trust is exactly what the root provides [2]. We treat that as an open task, not as finished work. We do not name hosts or services here.

For a generic example with a placeholder domain: in Caddy you write a site block with the address dienst.homelab.example and two lines inside it, tls internal and reverse_proxy backend:8080 [1][3]. That serves the service over HTTPS with a certificate from the local CA and forwards requests to the backend named backend. If you prefer a public CA, replace the first line with a tls block containing dns and your provider's name [3]. This is an illustration, not recorded output.

Checklist

  • Check whether every internal name resolves to the proxy and not to the individual backends.
  • Decide whether you use a public CA with DNS-01 or your own CA, and write down why.
  • Check whether every client device, including phones and containers, trusts your own root before you roll it out.
  • Limit the DNS API credentials to the TXT records you need, or delegate _acme-challenge to a separate zone.
  • Test certificate issuance against a staging endpoint first, so you do not hit the rate limits.
  • Check whether the backend receives the original scheme, host and client address, and whether it trusts only the proxy as the source of those headers.
  • Test WebSocket-based pages, and check idle timeouts and what happens on a config reload.
  • Check that management interfaces are not reachable from the internet and that backends accept traffic only from the proxy.
  • Update the proxy regularly and review its logs; write down which part of Zero Trust you cover and which you do not.

Primary sources

The sources back the core technical claims. The interpretation and checklist are our own work.

  1. Caddyreverse_proxy (Caddyfile directive)
  2. CaddyAutomatic HTTPS
  3. Caddytls (Caddyfile directive)
  4. Caddyforward_auth (Caddyfile directive)
  5. nginxWebSocket proxying
  6. nginxModule ngx_http_proxy_module
  7. Traefik LabsEntryPoints Documentation
  8. Traefik LabsTraefik and ACME Certificates Resolver
  9. Let's EncryptChallenge Types
  10. Let's EncryptRate Limits
  11. IETFRFC 8555: Automatic Certificate Management Environment (ACME)
  12. IETFRFC 7239: Forwarded HTTP Extension
  13. IETFRFC 6066: Transport Layer Security (TLS) Extensions: Extension Definitions
  14. NISTSP 800-207 Zero Trust Architecture

This page has no ads and no affiliate links.