
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.

```bash
tailscale serve --bg --https=443 http://127.0.0.1:8080
```

That 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.

```text
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.

## Read more

- [Tailscale serve](https://tailscale.com/kb/1312/serve)
- [Caddy reverse proxy quick start](https://caddyserver.com/docs/quick-starts/reverse-proxy) and [automatic HTTPS](https://caddyserver.com/docs/automatic-https)
- [Caddyfile concepts](https://caddyserver.com/docs/caddyfile/concepts)
