Caddy and a domain
Names instead of ports. Start on the tailnet, go public only when something needs to be public.
You know what a reverse proxy is. The decision on a home mini is not which one, it is whether anything needs to be reachable from the internet at all. Usually not, at first.
Tailnet only, no Caddy yet
If every client is a device you own, Tailscale can do the proxying
itself. tailscale serve puts a local port behind HTTPS on
your tailnet name with a real certificate, no domain, no open ports.
tailscale serve --bg --https=443 http://127.0.0.1:8080That is n8n or a dashboard at
https://mini.<your-tailnet>.ts.net from your phone on
cellular, and nobody else on earth can reach it. Start here.
Caddy, when you want a name
The moment you want notes.yourdomain.net, or something a
friend can open, or a webhook from the outside world, you want Caddy in
front of everything. A service is three lines and the certificate is
automatic.
notes.yourdomain.net {
reverse_proxy 127.0.0.1:8080
}
n8n.yourdomain.net {
reverse_proxy 127.0.0.1:5678
}
brew install caddy, put that in
/opt/homebrew/etc/Caddyfile,
brew services start caddy. For it to get certificates the
name has to resolve to your house and ports 80 and 443 have to reach the
mini, which means a dynamic DNS record at your registrar, a small
updater that keeps it current when your ISP changes your IP, and two
port forwards on the router.
Two things Jeff’s crew learned the hard way.
caddy reload when you change the file, never restart,
because a restart races for port 443 and can lose. And some home routers
will not route your own house to its new public IP for a while after it
changes, so everything looks down from inside while the internet sees it
fine. If every service dies at once right after an IP change, that is
the router, not you.
What goes public
Only what needs to. Anything public sits behind a login you trust, or is a static page with nothing sensitive on it. Everything else stays on the tailnet, which is most of it.
How you know it worked
curl -I https://notes.yourdomain.net from your phone on
cellular returns 200 with a valid certificate.
caddy validate --config /opt/homebrew/etc/Caddyfile says
valid before every reload.