If you're running more than three self-hosted services, manually port-forwarding each one and juggling NGINX conf files is no longer a workflow — it's a tax. Traefik 3 with Docker Compose replaces that whole mess with container labels and automatic Let's Encrypt certificates. No conf files. No certbot cron jobs. No remembering which service lives on which random port.
This is the setup I run on a Beelink mini-PC behind a single public IP, fronting roughly fifteen services. It took me about an hour to get right the first time, mostly because the Traefik docs assume you already know what an entrypoint is. This guide skips that.
What is Traefik 3 and why use it over NGINX?
Traefik 3: a cloud-native reverse proxy that auto-discovers Docker containers via labels and handles TLS certificate provisioning out of the box. NGINX, by comparison, needs hand-written config files per service and a separate certbot setup for HTTPS.
The win isn't raw performance — NGINX is faster on synthetic benchmarks and nobody who runs a homelab cares. The win is that adding a new service is a five-line label block on the container, not a new file in /etc/nginx/sites-available/ followed by a reload, a cert request, and the inevitable typo in server_name.
If you've already outgrown a single Caddy install or you want first-class Docker integration, Traefik is the pick. If you're hosting a static site on a VPS and that's it, stay with Caddy.
How do you set up Traefik 3 with Docker Compose? (high level)
You need a public domain, a server with ports 80 and 443 open, and Docker installed. The full setup is six steps:
- Point a wildcard DNS record (
*.yourdomain.com) at your server's public IP. - Create a dedicated Docker network so Traefik can reach every service container.
- Write a
traefik.ymlstatic config defining entrypoints and the Let's Encrypt resolver. - Write the Traefik
docker-compose.ymlwith the dashboard and ACME volume mounted. - Add router and service labels to each application container you want exposed.
- Bring it up with
docker compose up -dand verify HTTPS works on a test subdomain.
Each step below has the exact files. Copy, change the domain and email, ship.
Step 1: DNS and the Docker network
Buy a domain from Namecheap if you don't have one, then create an A record for *.yourdomain.com pointing at your server's public IP. Wildcard means every new service gets its hostname for free — no DNS edits per container.
Create the shared network:
docker network create proxyEvery container that Traefik should route to joins this network. Internal-only containers (databases, etc.) stay off it.
Step 2: The Traefik static config
Create the project directory and the static config file:
mkdir -p ~/traefik/letsencrypt
cd ~/traefik
touch letsencrypt/acme.json
chmod 600 letsencrypt/acme.jsonThe chmod 600 is non-negotiable — Traefik refuses to start if acme.json is world-readable, and the error message is not friendly. The docs mention this in passing; I learned it the slow way.
traefik.yml:
api:
dashboard: true
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
websecure:
address: ":443"
providers:
docker:
exposedByDefault: false
network: proxy
certificatesResolvers:
letsencrypt:
acme:
email: you@yourdomain.com
storage: /letsencrypt/acme.json
httpChallenge:
entryPoint: webexposedByDefault: false is the most important line. Without it, every container on the proxy network gets auto-routed, including the ones you didn't mean to expose. Opt-in only.
Step 3: The Traefik Compose file
This is the only Traefik service definition you'll write. After this, everything else is labels on other containers.
services:
traefik:
image: traefik:v3.2
container_name: traefik
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./traefik.yml:/traefik.yml:ro
- ./letsencrypt:/letsencrypt
networks:
- proxy
labels:
- "traefik.enable=true"
- "traefik.http.routers.dashboard.rule=Host(`traefik.yourdomain.com`)"
- "traefik.http.routers.dashboard.entrypoints=websecure"
- "traefik.http.routers.dashboard.tls.certresolver=letsencrypt"
- "traefik.http.routers.dashboard.service=api@internal"
- "traefik.http.routers.dashboard.middlewares=auth"
- "traefik.http.middlewares.auth.basicauth.users=admin:$$apr1$$xxxx"
networks:
proxy:
external: trueGenerate the basicauth hash with htpasswd -nb admin yourpassword and replace it. Double the dollar signs in Compose or Traefik will see ${var} placeholders. This bit me twice.
docker compose up -d and the dashboard is at https://traefik.yourdomain.com with a valid cert in about 30 seconds.
Step 4: Routing an actual service
Here's a real example — wiring up a Vaultwarden container behind Traefik:
services:
vaultwarden:
image: vaultwarden/server:latest
container_name: vaultwarden
restart: unless-stopped
volumes:
- ./vw-data:/data
networks:
- proxy
labels:
- "traefik.enable=true"
- "traefik.http.routers.vault.rule=Host(`vault.yourdomain.com`)"
- "traefik.http.routers.vault.entrypoints=websecure"
- "traefik.http.routers.vault.tls.certresolver=letsencrypt"
- "traefik.http.services.vault.loadbalancer.server.port=80"
networks:
proxy:
external: trueThat's it. No ports section. No NGINX edit. No certbot call. Run it and vault.yourdomain.com resolves with HTTPS within a minute.
The pattern repeats for every service: join the proxy network, add four labels, set the internal port. Adding a new service genuinely takes 30 seconds once you've done it twice. If you've used Glance for your homelab dashboard, it slots in identically.
Should you use Traefik vs NGINX for a homelab?
Use Traefik. For a homelab specifically, the label-based discovery model is the right abstraction — your services already live in Docker Compose files, and putting their routing config next to their container definition is how it should work.
NGINX still wins if you need very specific request rewriting, complex caching rules, or you're operating at a scale where the extra resource overhead of Traefik (it's a Go binary that uses ~100MB resident vs NGINX's ~10MB) matters. For everything else, the ergonomics win.
Caddy is the honourable mention. If you only need a handful of services and your Caddyfile is short, it's fine. Past about ten services, Caddy's lack of label-based discovery starts feeling clunky next to Traefik.
What about monitoring and uptime?
Traefik's dashboard shows routers and services but not historical uptime. Pair it with Uptime Kuma or Beszel to actually know when something's down. Both run as containers behind Traefik with the same label pattern.
For off-site backup of your acme.json and Compose files, push them to Backblaze B2 via Restic. Losing your Let's Encrypt state means re-requesting every cert and potentially hitting the weekly rate limit — back it up.
FAQs
Does Traefik 3 support wildcard certificates?
Yes, but only via DNS challenge — not the HTTP challenge shown above. You need to add a DNS provider plugin (Cloudflare, Route53, etc.) with API credentials and switch the resolver config to dnsChallenge. Worth doing if you have many subdomains or want to hide internal hostnames from cert transparency logs.
Can I run Traefik 3 without exposing my server to the internet?
Yes. Run Traefik behind Tailscale or WireGuard and use the DNS challenge for certs since the HTTP challenge needs public reachability on port 80. Many homelabbers do exactly this to get HTTPS on internal services without opening any ports.
Why does Traefik say "unable to obtain ACME certificate"?
Usually one of three things: port 80 isn't actually reachable from the internet (check your router and any cloud firewall), DNS hasn't propagated yet, or your acme.json isn't chmod 600. Check the Traefik logs with docker logs traefik — the error is verbose and tells you which check failed.
How do I migrate from NGINX to Traefik without downtime?
Run both side by side on different ports first. Move services one at a time by adding Traefik labels and updating DNS to point at Traefik's port once you've verified each one works. When everything's migrated, swap Traefik onto 80/443 and remove NGINX.
Is Traefik 3 production-ready for self-hosted services?
Yes — Traefik has been production-grade since v2 and v3 added HTTP/3, simplified middleware syntax, and better Kubernetes Gateway API support. Thousands of homelabs and small businesses run it as their only ingress. The configuration surface is smaller than NGINX, which means less to get wrong.