Skip to content

Reverse Proxy with Traefik

Traefik is a reverse proxy built for dynamic, container-based environments. Instead of editing a config file and reloading when you add a service, Traefik watches Docker and configures itself from labels on each container. New service up → route live, certificate issued, no proxy restart.

If you prefer a single hand-written config and maximum control, see Reverse Proxy with nginx. Traefik shines when your services are Docker containers that come and go.

Traefik splits config in two:

  • Static — set at startup: entrypoints (ports), providers (Docker), and the ACME (Let’s Encrypt) resolver. Changing it restarts Traefik.
  • Dynamic — routers, services, and middlewares, discovered at runtime from Docker labels (or files). Changing it does not restart Traefik.
# traefik.yml (static configuration)
entryPoints:
web:
address: ":80"
http:
redirections: # send all HTTP to HTTPS
entryPoint:
to: websecure
scheme: https
websecure:
address: ":443"
providers:
docker:
exposedByDefault: false # only route containers that opt in via labels
certificatesResolvers:
letsencrypt:
acme:
email: admin@example.com
storage: /letsencrypt/acme.json
httpChallenge:
entryPoint: web

Traefik itself is a container with access to the Docker socket so it can watch for other containers:

docker-compose.yml
services:
traefik:
image: traefik:v3.3
command: --configFile=/traefik.yml
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./traefik.yml:/traefik.yml:ro
- ./letsencrypt:/letsencrypt

Dynamic routing via labels + automatic certificates

Section titled “Dynamic routing via labels + automatic certificates”

A service opts into routing by adding labels — including which Host to match and which certificate resolver to use. Traefik then requests and renews the Let’s Encrypt certificate automatically:

app:
image: my/app
labels:
- "traefik.enable=true"
- "traefik.http.routers.app.rule=Host(`app.example.com`)"
- "traefik.http.routers.app.entrypoints=websecure"
- "traefik.http.routers.app.tls.certresolver=letsencrypt"
- "traefik.http.services.app.loadbalancer.server.port=8080"

That is the whole setup — no separate server {} block, no manual certbot run.

Middlewares transform requests/responses and chain together. Define them as labels and attach with routers.<name>.middlewares:

labels:
# Basic auth
- "traefik.http.middlewares.app-auth.basicauth.users=user:$$apr1$$..."
# Security headers
- "traefik.http.middlewares.app-sec.headers.stsSeconds=63072000"
- "traefik.http.middlewares.app-sec.headers.framedeny=true"
# Rate limit
- "traefik.http.middlewares.app-rl.ratelimit.average=10"
# Attach the chain to the router
- "traefik.http.routers.app.middlewares=app-auth,app-sec,app-rl"
Traefiknginx
ConfigurationAuto-discovered from Docker labelsHand-written files
CertificatesBuilt-in ACME (automatic)External (Certbot/acme.sh)
Reload on changeNot needed for routesnginx -s reload
Best forDynamic container fleetsStatic topologies, fine-grained tuning, raw throughput
Learning curveLabel syntax, two-tier configMature, vast documentation