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
FeatureTraefik v3Nginx Proxy Manager
Auto container discovery✅ Native❌ Manual
SSL certificates✅ Automatic✅ Automatic
Config styleLabels + YAMLGUI
HTTP/3
Learning curveMediumLow
MiddlewareExtensiveBasic

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 with traefik.enable=true
  • HTTP → HTTPS redirect — All port 80 traffic redirects to 443
  • http3: {} — Enables QUIC/HTTP3 on the websecure entrypoint
  • network: 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.json permissions: must be 600
  • Look at logs: docker compose logs traefik | grep -i acme

404 on a service

  • Confirm the container is on the traefik-public network
  • Check that traefik.enable=true is 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.insecure is false when using the router method
  • Try docker exec traefik traefik healthcheck

Migrating from Nginx Proxy Manager

If you’re moving from NPM:

  1. Deploy Traefik alongside NPM on different ports temporarily
  2. Add Traefik labels to each service one at a time
  3. Switch DNS or port forwarding to Traefik
  4. 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.