Traefik as a Reverse Proxy: Automatic HTTPS for All Your Services
How I replaced my Nginx reverse proxy config with Traefik and got automatic SSL certificates, service discovery, and a dashboard — with a fraction of the config.
For a long time, my reverse proxy setup was a pile of Nginx config files — one per service, each with its own SSL cert renewal cron job, each slightly different. It worked. It was also a maintenance headache.
Traefik replaced all of that with a single docker-compose.yml and automatic Let’s Encrypt certificate management. Here’s the setup that runs on my server today.
What Traefik Does
Traefik is a reverse proxy built specifically for container environments. The key difference from Nginx: it discovers services automatically through Docker labels. You don’t write a config file per service — you add labels to your containers and Traefik figures out the routing.
Add a service, it appears. Remove a container, its route disappears. No reload, no restart.
Prerequisites
- A VPS with Docker and Docker Compose installed
- A domain pointed at your server’s IP
- Ports 80 and 443 open in your firewall
The Core Traefik Setup
Create a directory for Traefik and its data:
mkdir -p ~/traefik/data
touch ~/traefik/data/acme.json
chmod 600 ~/traefik/data/acme.json # Critical — Traefik won't start without this
Static Configuration
Create ~/traefik/traefik.yml:
api:
dashboard: true
insecure: false
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
websecure:
address: ":443"
certificatesResolvers:
letsencrypt:
acme:
email: you@yourdomain.com
storage: /data/acme.json
httpChallenge:
entryPoint: web
providers:
docker:
exposedByDefault: false
file:
filename: /data/config.yml
watch: true
The exposedByDefault: false line is important — it means Traefik won’t automatically route to containers unless they explicitly opt in with labels.
Docker Compose
Create ~/traefik/docker-compose.yml:
version: "3.8"
networks:
proxy:
external: true
services:
traefik:
image: traefik:v3.0
container_name: traefik
restart: unless-stopped
security_opt:
- no-new-privileges:true
networks:
- proxy
ports:
- "80:80"
- "443:443"
volumes:
- /etc/localtime:/etc/localtime:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./traefik.yml:/traefik.yml:ro
- ./data/acme.json:/data/acme.json
- ./data/config.yml:/data/config.yml:ro
labels:
- "traefik.enable=true"
- "traefik.http.routers.traefik.rule=Host(`traefik.yourdomain.com`)"
- "traefik.http.routers.traefik.entrypoints=websecure"
- "traefik.http.routers.traefik.tls.certresolver=letsencrypt"
- "traefik.http.routers.traefik.service=api@internal"
- "traefik.http.routers.traefik.middlewares=auth"
- "traefik.http.middlewares.auth.basicauth.users=admin:$$2y$$05$$yourhashedpasswordhere"
Create the shared network first:
docker network create proxy
docker compose up -d
Adding a Service
Here’s where Traefik gets satisfying. Adding a new service behind your reverse proxy is just adding labels. Here’s Uptime Kuma as an example:
version: "3.8"
networks:
proxy:
external: true
services:
uptime-kuma:
image: louislam/uptime-kuma:1
container_name: uptime-kuma
restart: unless-stopped
networks:
- proxy
volumes:
- ./data:/app/data
labels:
- "traefik.enable=true"
- "traefik.http.routers.uptime.rule=Host(`status.yourdomain.com`)"
- "traefik.http.routers.uptime.entrypoints=websecure"
- "traefik.http.routers.uptime.tls.certresolver=letsencrypt"
That’s it. Traefik detects the container, fetches a certificate from Let’s Encrypt, and status.yourdomain.com is live over HTTPS. No Nginx config, no cert renewal cron jobs.
Useful Middlewares
Traefik’s middleware system lets you add functionality at the router level without modifying your application.
Security Headers
Create ~/traefik/data/config.yml:
http:
middlewares:
secure-headers:
headers:
sslRedirect: true
forceSTSHeader: true
stsIncludeSubdomains: true
stsPreload: true
stsSeconds: 31536000
contentTypeNosniff: true
browserXssFilter: true
referrerPolicy: "strict-origin-when-cross-origin"
Apply to any router:
- "traefik.http.routers.uptime.middlewares=secure-headers@file"
Rate Limiting
http:
middlewares:
rate-limit:
rateLimit:
average: 100
burst: 50
The Dashboard
Traefik’s dashboard at traefik.yourdomain.com shows all your routers, services, and middlewares in real time. It’s genuinely useful for debugging — you can see exactly what Traefik thinks is configured and why a route might not be matching.
Make sure your dashboard route has the BasicAuth middleware applied. An unprotected Traefik dashboard leaks your service topology to anyone who finds it.
What I Learned the Hard Way
The acme.json permissions matter. If it’s not 600, Traefik refuses to start. The error message isn’t obvious about why.
Let’s Encrypt rate limits are real. You get 5 certificate failures per hostname per hour before you’re temporarily blocked. Use the staging resolver (caServer: https://acme-staging-v02.api.letsencrypt.org/directory) while testing.
Don’t expose the Docker socket on a public-facing server without caution. Read-only socket access with a security proxy like socket-proxy is worth the extra setup on anything serious.
Traefik has made my self-hosted setup dramatically easier to manage. Adding a new service takes 10 lines of labels instead of a new Nginx config and a cert renewal job.