Introduction
I just published a step-by-step how-to in the Proxmox section: Traefik Reverse Proxy. This post is the “why” behind it, plus a few concepts I only half-understood before I started, written down here so they stick.
The problem I wanted to solve
My homelab had grown to the point where I was memorizing a list of IP addresses and port numbers: Proxmox on one address and port 8006, pfSense on another, Pi-hole, Home Assistant, Calibre, and Kimai, which I use every day. I wanted to type names like kimai.home.example.com instead, with a proper padlock in the browser rather than a wall of certificate warnings. I had three constraints. First, I did not want to open a single port on my router or expose any of these services to the internet. Second, I still wanted to reach everything from outside the house, using the Twingate connector I already had in place. Third, whatever I built had to play nicely with the Pi-hole ad blocker I host internally, which already answers every DNS question on my network.
Reverse proxy vs. forward proxy
The tool that made this possible is a reverse proxy, Traefik in my case. A proxy is simply a middleman for web traffic, and the difference between the two kinds is which side it works for. A forward proxy sits in front of clients: your browser hands it a request, and it goes out to the internet on your behalf. Corporate web filters and privacy proxies work this way, and the website on the other end sees the proxy, not you. A reverse proxy sits in front of servers: every request arrives at the proxy first, and it decides which server behind it should handle the request, so the client only ever sees the proxy. That second model fits my situation exactly. All my hostnames point to one address, Traefik’s. Traefik reads which name was requested (kimai., pve., pihole.…) and forwards the request to the right internal IP and port. It also handles HTTPS for all of them with a single wildcard certificate from Let’s Encrypt. Nothing is exposed to the internet, and Twingate only needs to know about one thing, *.home.example.com, to give me remote access to everything.
How Twingate gets me in from outside
Twingate is what lets me reach all of this from a coffee shop without opening my home network to the internet. It’s often compared to a VPN, but it works differently in two important ways. It has three parts: a small client app on my laptop and phone, a connector running inside my home network, and Twingate’s cloud service, which checks who I am and brokers the connection. Because the connector only ever makes outbound connections to Twingate’s cloud, there are no ports to open on my router and nothing at home for an internet scanner to find. The first difference from a VPN is scope: instead of putting my laptop “on” my home network, Twingate grants access to specific resources. In my case that’s a single one, *.home.example.com on the web ports, and only my user is allowed to use it. The second difference is that only traffic for those resources goes through the tunnel; everything else goes straight to the internet as usual. When I type kimai.home.example.com with the client running, the client recognizes the name as a resource and intercepts it. It sends the request through an encrypted tunnel to my connector at home, which looks the name up through Pi-hole and connects to Traefik on my behalf. I tested this from my Starlink connection: with Twingate off, the name still resolved to Traefik’s private address (the public DNS record is there for everyone to see), but the connection went nowhere, because private addresses can’t be reached across the internet. With Twingate on, Kimai loaded with a valid padlock. The neatest way I’ve found to put it: DNS tells you where; Twingate gets you there.
How DNS actually finds an address
To make names work, I had to understand DNS far better than I did. When I type kimai.home.example.com, my Mac asks its DNS server for the matching IP address. If that server doesn’t already know the answer, a recursive resolver goes looking for it through a chain of referrals. It starts at one of the 13 root servers (run by organizations such as Verisign, ICANN and NASA, each copied to hundreds of locations worldwide). The root doesn’t know the answer, but it knows who runs .com. The .com servers don’t know the answer either, but they know which nameservers are responsible for example.com, because my registrar told them when I set the domain up. Finally, those nameservers give the actual answer. You can watch the whole chain happen with dig +trace:
$ dig +trace kimai.home.example.com (trimmed for readability)
. NS a.root-servers.net.
;; from my local resolver: "here are the root servers"
com. NS a.gtld-servers.net.
;; from d.root-servers.net: "ask the .com servers"
example.com. NS your-ns1.ns.cloudflare.com.
;; from k.gtld-servers.net: "ask Cloudflare"
kimai.home.example.com. 300 A 192.168.10.20
;; from Cloudflare: "here is the address"
Each answer comes with a TTL, a “cache this for N seconds” timer (300 seconds in that last line), so resolvers don’t repeat the whole walk on every request. The surprise for me was that there is no “propagation” in the sense I had imagined. My record isn’t copied around the world. It lives in one place, and every resolver on earth finds it by following the same referrals.
What Pi-hole really does
That clarified something I had misunderstood for years. Pi-hole doesn’t filter URLs, and it doesn’t resolve them either. It is a DNS server that sits in the path of every name lookup on my network. It only ever sees the hostname, like ads.tracker.net, never the full URL with its path. That’s why it can block an ad domain, but not an ad served from the same domain as the video I’m watching. When a lookup arrives, Pi-hole checks it against its blocklists. If the name is blocked, Pi-hole answers immediately with a dead-end address (0.0.0.0), and the ad simply never loads. If the name matches one of its own local records, or something it has cached, it answers that too. Everything else it forwards to an upstream resolver, which does the root-to-answer walk described above, and Pi-hole relays the result back. For this project, I gave Pi-hole a local wildcard record that points *.home.example.com straight at Traefik. Now my hostnames are answered inside the house without asking the internet at all, which means they keep working even when my ISP is down.
Read the how-to
The full build, including the detours (a domain moved to Cloudflare, a router that went red, Docker refusing to run inside a Proxmox container, and a Pi-hole setting the web UI wouldn’t let me change), is in the Proxmox section: Traefik Reverse Proxy