On this page
By the time your homelab is running a handful of services, you’re juggling a spreadsheet of IP addresses and port numbers: 10.0.0.20:3000 for Grafana, 10.0.0.21:8080 for something else, none of them on HTTPS. A reverse proxy fixes all of that. This post explains what a reverse proxy is, why Traefik is a great fit for homelabs, and how the pieces fit together. When you’re ready to build it, How to Set Up Traefik with Let’s Encrypt is the hands-on next post in this series.
The problem a reverse proxy solves
Without a reverse proxy, every service is a different IP:port, each with its own untrusted certificate warning (or no HTTPS at all). You memorise ports, browsers nag, and nothing has a real name.
With a reverse proxy, everything lives behind one HTTPS endpoint and gets routed by name.
Why Traefik for a homelab
There are three common choices — all good, with different strengths:
- Traefik — discovers services automatically. Add a Docker container with a few labels and Traefik starts routing to it instantly, no config reload. Perfect when you add services often.
- Nginx Proxy Manager — a friendly web GUI for people who’d rather click than edit YAML.
- Caddy — the simplest static config file, HTTPS on by default.
Still deciding between them? Traefik vs Nginx Proxy Manager vs Caddy puts all three side by side.
Traefik’s edge is that dynamic discovery. Because your Docker apps declare their own routing via labels, the proxy config effectively maintains itself. That pairs naturally with a Docker host, which is why it’s the reverse proxy this series sets up.
| Traefik | Nginx Proxy Manager | Caddy | |
|---|---|---|---|
| How you configure it | Docker labels + a small static file | Web GUI (click through) | One plain text Caddyfile |
| Auto-discovers services | Yes — the standout feature | No (add each in the UI) | No (edit the file) |
| Automatic HTTPS | Yes | Yes | Yes, on by default |
| Learning curve | Steepest of the three | Gentlest | Gentle |
| Best when | You add/remove services often | You want zero YAML | You want the simplest single file |
None of these is wrong. If you rarely change your stack, Caddy or NPM will feel simpler. Traefik earns its slightly steeper start the day you realise you haven’t touched the proxy config in months despite adding five new services — each one wired itself up on first boot.
You don’t write a route for each app — the app describes itself. A container just adds labels like these and Traefik does the rest:
labels:
- "traefik.enable=true"
- "traefik.http.routers.wiki.rule=Host(`wiki.homelab.lan`)"
- "traefik.http.routers.wiki.tls.certresolver=letsencrypt"
The three pieces you need
To go from “IP and port” to https://service.homelab.lan, three things work together:
Your devices need to know that wiki.homelab.lan points at the proxy. That’s exactly what Pi-hole local DNS records provide — point every service hostname at the Traefik host’s IP.
Traefik receives every request on port 443 and forwards it to the correct backend based on the Host() rule — the routing layer in the diagram above.
Let’s Encrypt issues free, browser-trusted certificates so there are no more security warnings. Traefik requests and renews them automatically via its ACME/Let’s Encrypt integration.
Static vs dynamic config: the one idea that clears up the confusion
The single thing that trips up Traefik newcomers is that it reads two kinds of configuration, and they do different jobs:
- Static config is read once, at startup (from
traefik.ymlor command-line flags). It defines the things that can’t change while Traefik runs: the entrypoints (the ports it listens on —:80and:443), the providers it should watch (Docker, a config file, etc.), and the certificate resolvers (your Let’s Encrypt/ACME settings). Change any of these and Traefik must restart. - Dynamic config is read continuously, while Traefik runs: the routers (which hostname goes where), services (the backends), and middlewares (auth, redirects, rate limits). This is what the Docker labels produce, and Traefik applies changes here with no restart.
Once that clicks, everything else makes sense: you set up entrypoints and your Let’s Encrypt resolver once in the static file, then every new app just adds a router in the dynamic layer via labels.
Define the :443 HTTPS entrypoint and the :80 HTTP one in static config, then have the HTTP entrypoint permanently redirect to HTTPS. Do it once at the entrypoint and every current and future service is covered — you never think about plain-HTTP again.
Routing services that don’t run in Docker
On a Proxmox homelab, plenty of things you want a clean URL for aren’t Docker containers — a service in its own LXC, an appliance, a box on another host. Traefik’s label discovery only sees Docker, so for everything else you use the file provider: a small dynamic config file where you write the router and point it at an explicit backend address.
http:
routers:
proxmox:
rule: "Host(`pve.homelab.lan`)"
service: proxmox
tls:
certResolver: letsencrypt
services:
proxmox:
loadBalancer:
servers:
- url: "https://10.0.0.10:8006"
Point the file provider at this file in static config and Traefik watches it live, just like it watches Docker. This is the piece most “Traefik is only for Docker” write-ups leave out — and it’s what lets one proxy front your entire lab, containers and LXCs alike.
The gotchas that actually bite
A short list of the things that cost people an evening:
- Traefik and the app must share a Docker network. Auto-discovery only routes to containers Traefik can actually reach. A container on a different network is invisible — put them on a shared proxy network (or set
traefik.docker.networkon the service). - The Docker socket is a root-equivalent door. Traefik reads
/var/run/docker.sockto discover containers — anything with that socket effectively controls the host. Mount it read-only, and for anything internet-facing put a socket proxy in front of it rather than handing Traefik the raw socket. - Internal-only names need the DNS challenge, not HTTP. Let’s Encrypt’s HTTP-01 challenge needs a publicly reachable port 80; a purely internal
homelab.lanservice can’t answer it. Use the DNS-01 challenge instead (and it’s the only way to get a wildcard cert like*.homelab.lan). - Secure the dashboard. Traefik’s dashboard is handy but must never be exposed with
api.insecure=trueon anything reachable from outside — put it behind the same HTTPS + auth as everything else. - A label typo fails silently. A malformed router label doesn’t error loudly — the route just never appears. When a service 404s, check the Traefik dashboard (it lists every router it loaded) and the container logs before touching anything else.
Put Traefik in front
Now that you understand the why, How to Set Up Traefik with Let’s Encrypt puts it into practice: deploying Traefik, wiring up automatic certificates, and routing your first service to a clean HTTPS URL. The official Traefik documentation is the best deeper reference.
Related posts:
- How to Set Up Traefik with Let’s Encrypt — the hands-on deployment walkthrough.
- Docker on Proxmox: LXC vs VM, Done Right — the host Traefik discovers services on.
- Pi-hole on Proxmox LXC — the local DNS names Traefik routes.
- Proxmox Firewall: Zone-Based Rules — the layer a reverse proxy complements, not replaces.
- Tailscale Subnet Router — reach your proxied services securely from anywhere.
- Vaultwarden on Proxmox LXC — a great first service to put behind Traefik.
- What Is Authentik? — add a single sign-on login in front of the apps Traefik routes.
Sources: Traefik documentation, Let’s Encrypt.
Comments
Comments are powered by GitHub Discussions — sign in with a GitHub account to join the conversation.