Ramblings of an aging IT geek
← Ramblings of an aging IT geek
homelab

reverse proxy, certs, and let's encrypt at home

Getting trusted TLS on internal homelab services without opening ports, using a wildcard cert and DNS-01 validation.

A server rack with patch cables running between switches

I run a handful of services at home that I would rather not expose to the internet: a dashboard, some media tooling, the bits and pieces every homelab accretes. For years I lived with self-signed certificates and the browser warnings that come with them, clicking through "your connection is not private" so often I stopped reading it. That is exactly the muscle memory you do not want to build, so I fixed it.

The trick is that you do not need to expose anything to get a real certificate. The usual HTTP-01 challenge wants Let's Encrypt to reach your server on port 80, which means opening a port, which I am not doing for internal-only services. DNS-01 sidesteps it entirely. You prove you control the domain by writing a TXT record, and you can do that for a domain whose A records point at RFC1918 addresses that the wider internet can never reach.

A homelab cabinet with a small server and network gear

So the shape is: one wildcard certificate for *.home.example.com, issued by Let's Encrypt over DNS-01, terminated at a single reverse proxy. Internal DNS resolves the names to the proxy, the proxy holds the cert and forwards to each backend over plain HTTP on the LAN. Every service gets a proper green padlock and I never click through a warning again.

With Caddy the configuration is almost insultingly short, because the DNS provider plugin handles the challenge and renewal for you:

*.home.example.com {
  tls {
    dns cloudflare {env.CF_API_TOKEN}
  }

  @grafana host grafana.home.example.com
  handle @grafana {
    reverse_proxy 10.0.10.20:3000
  }

  @nas host nas.home.example.com
  handle @nas {
    reverse_proxy 10.0.10.30:5000
  }
}

The API token wants to be scoped to DNS edit on that one zone and nothing else. If it leaks, the blast radius is "someone can mess with TXT records on a domain that only resolves on my LAN", which I can live with. Do not hand it a global key.

Renewal is automatic and silent, which is the whole point. The certificate is short-lived, the proxy renews it well before expiry, and the only time I think about TLS now is when I add a new service and copy-paste another four lines. The browser warning that I had trained myself to ignore is gone, and along with it the small daily lie that "it's only internal, it doesn't matter". It always mattered. Now it is just sorted.