If you’re running multiple Docker services and juggling Nginx configs every time you add a container, Traefik will change your life. It watches Docker for new containers and automatically configures routing — no config file edits, no reloads, no downtime.
What is Traefik?
Traefik is a modern reverse proxy and load balancer designed for microservices and containers. Version 3 brought HTTP/3 support, WASM plugins, and a cleaner configuration model. It sits in front of all your services, handles SSL termination, and routes traffic based on Docker labels.
Why Traefik Over Nginx Proxy Manager?
- Auto-discovery: Add a container with labels → Traefik routes it instantly
- No GUI required: Everything is config-as-code (but there’s a dashboard too)
- Let’s Encrypt built-in: Automatic certificate provisioning and renewal
- Middleware chains: Rate limiting, auth, headers, redirects — all declarative
- HTTP/3 support: QUIC protocol out of the box in v3
| Feature | Traefik v3 | Nginx Proxy Manager |
|---|---|---|
| Auto container discovery | ✅ Native | ❌ Manual |
| SSL certificates | ✅ Automatic | ✅ Automatic |
| Config style | Labels + YAML | GUI |
| HTTP/3 | ✅ | ❌ |
| Learning curve | Medium | Low |
| Middleware | Extensive | Basic |
Prerequisites
- A Linux server (Ubuntu 22.04+, Debian 12+, or similar)
- Docker and Docker Compose installed
- A domain name pointing to your server’s IP
- Ports 80 and 443 available
Step 1: Project Structure
Create a directory for Traefik:
mkdir -p ~/traefik && cd ~/traefik
You’ll need three files: a Docker Compose file, a static configuration, and an empty file for certificates.
touch acme.json
chmod 600 acme.json
The acme.json file stores your Let’s Encrypt certificates. The strict permissions are required — Traefik won’t start without them.
Step 2: Static Configuration
Create traefik.yml — this is Traefik’s static configuration that defines entrypoints and providers:
# traefik.yml
api:
dashboard: true
insecure: false
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
websecure:
address: ":443"
http3: {}
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
network: traefik-public
certificatesResolvers:
letsencrypt:
acme:
email: [email protected]
storage: /acme.json
httpChallenge:
entryPoint: web
Key settings:
exposedByDefault: false— Containers must opt-in withtraefik.enable=true- HTTP → HTTPS redirect — All port 80 traffic redirects to 443
http3: {}— Enables QUIC/HTTP3 on the websecure entrypointnetwork: traefik-public— Traefik only sees containers on this network
Step 3: Docker Compose
Create docker-compose.yml:
version: "3.9"
services:
traefik:
image: traefik:v3.2
container_name: traefik
restart: unless-stopped
security_opt:
- no-new-privileges:true
ports:
- "80:80"
- "443:443"
- "443:443/udp" # HTTP/3
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./traefik.yml:/traefik.yml:ro
- ./acme.json:/acme.json
networks:
- traefik-public
labels:
# Dashboard
- "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"
# Basic auth for dashboard
- "traefik.http.routers.dashboard.middlewares=dashboard-auth"
- "traefik.http.middlewares.dashboard-auth.basicauth.users=admin:$$2y$$10$$HASH_HERE"
networks:
traefik-public:
external: true
Create the network before starting:
docker network create traefik-public
Generate a password hash for the dashboard:
sudo apt install apache2-utils -y
htpasswd -nB admin
Replace $$2y$$10$$HASH_HERE with the output (double every $ sign in the YAML).
Step 4: Start Traefik
docker compose up -d
Check the logs:
docker compose logs -f traefik
You should see Traefik discover itself and provision an SSL certificate for your dashboard domain. Visit https://traefik.yourdomain.com to see the dashboard.
Step 5: Route Your First Service
Here’s how to add any Docker service behind Traefik. Example with Whoami (a simple test container):
# whoami/docker-compose.yml
version: "3.9"
services:
whoami:
image: traefik/whoami
container_name: whoami
restart: unless-stopped
networks:
- traefik-public
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami.rule=Host(`whoami.yourdomain.com`)"
- "traefik.http.routers.whoami.entrypoints=websecure"
- "traefik.http.routers.whoami.tls.certresolver=letsencrypt"
networks:
traefik-public:
external: true
That’s it. No Traefik restart needed. The container joins the traefik-public network, Traefik detects it, reads the labels, provisions an SSL cert, and starts routing traffic. Zero downtime.
Step 6: Useful Middlewares
Traefik’s middleware system is where it really shines. Add these to any router:
Rate Limiting
labels:
- "traefik.http.middlewares.rate-limit.ratelimit.average=100"
- "traefik.http.middlewares.rate-limit.ratelimit.burst=50"
- "traefik.http.routers.myapp.middlewares=rate-limit"
Security Headers
labels:
- "traefik.http.middlewares.secure-headers.headers.stsSeconds=31536000"
- "traefik.http.middlewares.secure-headers.headers.stsIncludeSubdomains=true"
- "traefik.http.middlewares.secure-headers.headers.contentTypeNosniff=true"
- "traefik.http.middlewares.secure-headers.headers.browserXssFilter=true"
- "traefik.http.middlewares.secure-headers.headers.frameDeny=true"
- "traefik.http.routers.myapp.middlewares=secure-headers"
IP Allowlist (Internal Services)
labels:
- "traefik.http.middlewares.local-only.ipallowlist.sourcerange=192.168.1.0/24,10.0.0.0/8"
- "traefik.http.routers.myapp.middlewares=local-only"
Chaining Middlewares
Apply multiple middlewares to one router:
labels:
- "traefik.http.routers.myapp.middlewares=rate-limit,secure-headers,dashboard-auth"
Step 7: Wildcard SSL with DNS Challenge
For wildcard certificates (*.yourdomain.com), switch from HTTP challenge to DNS challenge. Example with Cloudflare:
# Add to traefik.yml
certificatesResolvers:
letsencrypt:
acme:
email: [email protected]
storage: /acme.json
dnsChallenge:
provider: cloudflare
resolvers:
- "1.1.1.1:53"
- "8.8.8.8:53"
Add your Cloudflare API token to the compose environment:
services:
traefik:
environment:
- CF_DNS_API_TOKEN=your-cloudflare-api-token
Then use a wildcard in your router:
labels:
- "traefik.http.routers.myapp.tls.domains[0].main=yourdomain.com"
- "traefik.http.routers.myapp.tls.domains[0].sans=*.yourdomain.com"
Troubleshooting
Certificate not provisioning
- Check that port 80 is reachable from the internet (for HTTP challenge)
- Verify DNS points to your server:
dig yourdomain.com - Check
acme.jsonpermissions: must be600 - Look at logs:
docker compose logs traefik | grep -i acme
404 on a service
- Confirm the container is on the
traefik-publicnetwork - Check that
traefik.enable=trueis set - Verify the
Host()rule matches your DNS - If the container exposes multiple ports, specify:
traefik.http.services.myapp.loadbalancer.server.port=8080
502 Bad Gateway
- The backend container might not be ready yet
- Add a health check to your service
- Check if the service is listening on the right port inside the container
Dashboard not loading
- Verify the basicauth hash has doubled
$signs in YAML - Check that
api.insecureisfalsewhen using the router method - Try
docker exec traefik traefik healthcheck
Migrating from Nginx Proxy Manager
If you’re moving from NPM:
- Deploy Traefik alongside NPM on different ports temporarily
- Add Traefik labels to each service one at a time
- Switch DNS or port forwarding to Traefik
- Remove NPM once everything’s verified
Don’t try to migrate everything at once. Do one service, verify it works, then move the next.
Backing Up Traefik
The only critical file is acme.json — it holds your certificates:
cp acme.json acme.json.backup
Your configuration is in traefik.yml and your compose files. If you’re using git for your Docker configs (you should be), everything is already tracked.
Conclusion
Traefik v3 is the best reverse proxy for Docker-heavy setups. The auto-discovery alone saves hours of config management — no more editing Nginx files and reloading every time you spin up a container. Add HTTP/3 support, built-in Let’s Encrypt, and a powerful middleware system, and it’s hard to argue against it.
Start with the basic setup, route one service, and expand from there. Once you experience the “add labels and it just works” workflow, you won’t go back to manual proxy configs.