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

real certs for the things on my own network

Putting a single reverse proxy in front of the homelab and getting proper Let's Encrypt certificates on internal services without poking holes in the firewall.

A server rack with cabling

I was tired of clicking through certificate warnings on my own kit. Every self-hosted thing in the house had its own half-baked TLS, either a self-signed cert the browser screamed about or plain HTTP because I had given up. It worked, but it was scruffy, and I had started to wince every time I demonstrated something to anyone and had to click "yes I really do trust this dodgy cert on my own router".

The answer was a single reverse proxy out front. One box that terminates TLS, holds the real certificates, and routes by hostname to whatever is actually serving the request behind it. Everything internal can stay plain HTTP on the back side where it never leaves my own switch, and the only thing holding certs is the proxy.

the proxy

I went with nginx because I already knew it and the config is boring in the good way. Each service gets a server block keyed on its hostname.

server {
    listen 443 ssl;
    server_name git.home.example.com;

    ssl_certificate     /etc/letsencrypt/live/home.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/home.example.com/privkey.pem;

    location / {
        proxy_pass http://10.0.0.21:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto https;
    }
}

Nothing exotic. The interesting part was getting certificates onto it without exposing the services to the internet, which I very much did not want to do.

the DNS-01 trick

The usual Let's Encrypt flow proves you control a domain by serving a file over HTTP on port 80, which means opening port 80 to the world. For internal-only services that is daft, and in some cases impossible. The answer is the DNS-01 challenge: instead of serving a file, you prove control by writing a TXT record into DNS. My registrar has an API, so certbot can do this automatically.

certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/cf.ini \
  -d home.example.com -d '*.home.example.com'

A homelab shelf of mixed equipment

That wildcard is the bit I am pleased about. A single *.home.example.com cert covers every service I have and every one I have not built yet, and nothing ever needs to be reachable from outside. The names resolve only on my internal DNS, the certs are real and trusted, and not a single port is forwarded.

A few things worth knowing if you try this:

  • Wildcard certificates require the DNS-01 challenge specifically; you cannot get a wildcard via the HTTP method. That alone is reason enough to wire up the DNS API.
  • Point your internal DNS resolver at the proxy for all those hostnames so the names work on the LAN without ever leaving it.
  • Put the renewal on a timer and actually check it fires. A cert that silently fails to renew is worse than no cert, because you will trust it right up until the morning it expires.

was it worth it

Completely. The certificate warnings are gone, every internal service has its own clean https://something.home.example.com name, and the renewal runs itself in the background where I never think about it. Most satisfying of all, I no longer have to apologise for my own network when I show someone a thing I built. It just loads, with a padlock, like a grown-up service. For an afternoon's work and a config file full of boring server blocks, that is a very good trade.