Networking: proxy & split-horizon DNS
Four pieces work together so every service gets a clean https://app.example.com address that’s fast at home, reachable anywhere, and exposed to the internet only when you choose:
- A reverse proxy terminates TLS and routes by hostname.
- A local DNS server points those hostnames at the proxy (split-horizon).
- A mesh VPN makes them reachable off-LAN, privately.
- A tunnel publishes the few you want public — no port-forwarding.
Reverse proxy
Section titled “Reverse proxy”Run a proxy (this lab uses Caddy for its automatic TLS) that terminates HTTPS once and forwards to each backend by IP:port. One wildcard cert, one place for routing.
# private — only reachable on LAN / VPN (no public tunnel)app.example.com { reverse_proxy 10.0.0.20:8080}
# public — also served over the tunnel (dual scheme)public-app.example.com { reverse_proxy 10.0.0.21:3000 { header_up X-Forwarded-Proto https }}Get the wildcard cert via a DNS-01 ACME challenge (your DNS provider’s API), so you never expose port 80/443 to get certificates. Backends are addressed by IP, so the proxy needs no internal name resolution.
Split-horizon DNS
Section titled “Split-horizon DNS”Here’s the trick that makes it feel seamless: run a local DNS server (this lab uses Technitium; Pi-hole/AdGuard also work) and have *.example.com resolve to the proxy’s LAN IP for anyone on your network. Same names, private path.
*.example.com → A → 10.0.0.10 (the reverse proxy)Now app.example.com resolves to the proxy locally and to whatever’s public (or nothing) from the outside.
This also gets you network-wide ad/tracker blocking for free (block lists on the same resolver) and per-device rules (allow a smart TV’s telemetry, block everything else).
Private by default: mesh VPN
Section titled “Private by default: mesh VPN”Put the box (and your devices) on a mesh VPN like Tailscale. Now every private service is reachable from your phone anywhere, with no ports open to the internet. Combined with split-horizon, app.example.com just works on the couch or on cellular.
Point the VPN’s DNS at your local resolver so the split-horizon names resolve over the tunnel too.
Public by exception: a tunnel
Section titled “Public by exception: a tunnel”For the handful of services that must be public (a status page, something you share), use an outbound tunnel (e.g. Cloudflare Tunnel) instead of forwarding ports:
- The tunnel daemon dials out to the edge; no inbound ports, no home IP exposed.
- Map only the specific hostnames you want public to the proxy; everything else stays private.
- You get DDoS protection and a WAF in front for free.
The mental model
Section titled “The mental model” ┌──────────── public visitor │ (tunnel, selected hosts only) internet ─┤ │ ┌───────── you, anywhere └──┤ (mesh VPN → everything, private) │ LAN device ──┴─→ local DNS (*.example.com → proxy IP, + ad-block) │ └─→ reverse proxy (TLS) ─→ backend:portOne domain, one proxy, one resolver. Names are identical whether you’re on the couch, on cellular, or (for the chosen few) a stranger on the internet — the path differs, the address doesn’t.