Learn Traefik in a Single Post: Complete Tutorial From Dynamic Routing and Middleware to ACME TLS

Traefik is the cloud-native reverse proxy — a modern alternative to Nginx designed for containerized environments. Its defining feature: dynamic configuration — it reads routing rules from Docker labels, Kubernetes Ingress, or config files at runtime, and updates routes instantly as containers start and stop, with no config-file edit and no reload. It also has built-in Let’s Encrypt ACME for automatic TLS certificates. This single post covers the whole proxy in five stages, with hand-drawn diagrams and runnable config.

Learning Roadmap

Traefik Roadmap

The roadmap moves from entrypoints (Stage 1), through routers (Stage 2), services (Stage 3), middleware (Stage 4), and production (Stage 5). The Nginx tutorial and Docker tutorial are companions.


Stage 1 — Entrypoints

Listening on ports

An entrypoint is a port Traefik listens on. The defaults are:

# traefik.yml (static config)
entryPoints:
  web:
    address: ":80"
  websecure:
    address: ":443"
  • web (:80) — HTTP (usually redirected to HTTPS).
  • websecure (:443) — HTTPS (TLS terminated here).

Entrypoints are defined in the static config (traefik.yml / CLI flags) — they change rarely (a restart is needed to add/remove them). Everything else (routers, services, middleware) is dynamic — updated at runtime without restart.


Stage 2 — Routers

Routing rules

Traefik Architecture: Dynamic Routing

A router matches incoming requests by rule and routes them to a service:

# dynamic config (file provider)
http:
  routers:
    api-router:
      rule: "Host(`api.example.com`) && PathPrefix(`/v1`)"
      entryPoints:
        - websecure
      service: api-service
      tls: certResolver: letsencrypt
      middlewares:
        - rate-limit
        - auth

Rule syntax

Rule Matches
Host(\example.com`)` requests to this domain
PathPrefix(\/api`)` requests starting with /api
Path(\/exact/path`)` exact path match
HostRegexp(\{subdomain:[a-z]+}.example.com`)` regex host match
Method(\POST`)` HTTP method
Headers(\X-Custom`, `value`)` header match

Rules can be combined with && (AND) and \|\| (OR), and grouped with (). Priority can be set explicitly (priority: 100) — by default, longer/more-specific rules win.

Dynamic config from Docker labels

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

When this container starts, Traefik automatically creates a router for api.example.com → the container’s port 8080. When the container stops, the router is removed. No config file edit, no reload — this is Traefik’s core advantage over Nginx.


Stage 3 — Services

Load balancing to backends

http:
  services:
    api-service:
      loadBalancer:
        healthCheck:
          path: /health
          interval: 10s
        servers:
          - url: http://api1:8080
          - url: http://api2:8080
          - url: http://api3:8080

A service defines where traffic goes — a pool of backend servers with load balancing. Traefik supports:

  • Round-robin (default) — distribute evenly.
  • Weightedweight: 3 on one server (canary deployments).
  • Sticky sessionssticky.cookie for session affinity.
  • Health checks — periodically check /health; remove unhealthy servers.

With the Docker provider, Traefik auto-discovers backend containers (it reads their IP + port from Docker’s API) — you don’t manually list server URLs. The service is created from the container’s labels.


Stage 4 — Middleware

Chain of processing before the service

Middleware: Chain of Processing Before the Service

Middleware processes requests before they reach the service — and responses on the way back. Multiple middleware can be chained on a router:

http:
  routers:
    api-router:
      rule: "Host(`api.example.com`)"
      service: api-service
      middlewares:
        - redirect-to-https
        - rate-limit
        - auth
        - compress

Common middleware

Middleware What it does
RateLimit requests/sec per source IP — protect backends from spikes
BasicAuth / ForwardAuth validate credentials / delegate to an external auth service
Compress gzip/brotli response compression
Retry retry failed upstream requests (resilience)
Headers add/remove headers (HSTS, CORS, security headers)
CircuitBreaker stop sending to a failing backend (fail fast, let it recover)
RedirectScheme redirect HTTP → HTTPS
StripPrefix / ReplacePath rewrite the URL before it reaches the backend
IPAllowList only allow specific IPs

ForwardAuth — delegate to an external auth service

http:
  middlewares:
    auth:
      forwardAuth:
        address: http://auth-service:8080/check
        authResponseHeaders:
          - X-Auth-User

ForwardAuth sends each request to an auth service first; if the auth service returns 2xx, the request proceeds to the backend; if it returns 401/403, the request is rejected. This is how you integrate OIDC/OAuth (OAuth tutorial) — the auth service validates the token, and Traefik forwards the authenticated request.


Stage 5 — Production

Production: TLS ACME, Dashboard, Metrics, HA

ACME — automatic Let’s Encrypt TLS

# traefik.yml
certificatesResolvers:
  letsencrypt:
    acme:
      email: admin@example.com
      storage: /acme.json
      httpChallenge:
        entryPoint: web

Traefik automatically obtains and renews Let’s Encrypt TLS certificates. When a router with tls.certresolver=letsencrypt receives a request for a new domain, Traefik initiates the ACME challenge, gets the certificate, and serves HTTPS — all without manual certbot runs. Certificates are stored in acme.json (shared across instances for HA).

Challenge types:

  • HTTP-01 — Traefik serves the challenge on port 80 (simplest, works through most firewalls).
  • TLS-01 — challenge via TLS ALPN.
  • DNS-01 — challenge via DNS TXT record (required for wildcard certs *.example.com).

Dashboard

# traefik.yml
api:
  dashboard: true
  insecure: false    # serve via a router, not on a public port

# router for the dashboard
http:
  routers:
    dashboard:
      rule: "Host(`traefik.example.com`)"
      service: api@internal
      middlewares:
        - dashboard-auth

Traefik has a built-in web dashboard at /dashboard/ showing live routers, services, middleware, and entrypoints. Secure it with BasicAuth or ForwardAuth — never expose it publicly without auth.

Metrics

metrics:
  prometheus:
    addEntryPointsLabels: true
    addServicesLabels: true
    addRoutersLabels: true

Traefik exposes a Prometheus /metrics endpoint — request count, latency, status codes per router/service/entrypoint. This integrates directly with the Prometheus + Grafana stack.

High availability

Run 2+ Traefik instances behind a load balancer (or with a VIP). Share the ACME storage (acme.json on a shared volume or in a KV store like Consul) so instances don’t duplicate cert issuance. Use a KV store (Consul, Redis, etcd) for shared dynamic config — all instances read the same routing rules.

Traefik vs Nginx

  Traefik Nginx
Config dynamic (Docker/K8s labels, no reload) static file + nginx -s reload
TLS built-in ACME (Let’s Encrypt auto) needs certbot externally
Service discovery auto (reads Docker/K8s API) manual (edit upstream list)
Cloud-native designed for containers general-purpose (works everywhere)
Fine control less tweakable (opinionated) more control over edge cases
Performance fast (Go, built for it) fast (C, battle-tested)
Best for containerized/Docker/K8s traditional apps, custom edge cases

Use Traefik when you’re in a containerized world (Docker/K8s) and want auto-discovery + auto-TLS with zero manual config. Use Nginx when you need maximum control, have non-container backends, or have complex edge requirements Traefik’s abstraction can’t express.

Pitfall: Traefik’s dynamic config is powerful but can be hard to debug — when a route doesn’t work, you need to check the dashboard to see if the router was created, what rule it has, and which middleware is attached. Nginx’s config file is right there to read. For complex setups, Traefik’s abstraction can hide what’s happening.


Quick-Start Checklist

  1. Run Traefik in Dockerdocker run -p 80:80 -p 443:443 -v /var/run/docker.sock:/var/run/docker.sock traefik:v3.
  2. Open the dashboardhttp://localhost:8080/dashboard/ (or route it via a router).
  3. Label a containertraefik.http.routers.app.rule=Host(\localhost`) + traefik.http.services.app.loadbalancer.server.port=8080`.
  4. Watch it appear in the dashboard — the router is created automatically.
  5. Add middleware — rate-limit, compress, or auth on the router.
  6. Enable ACMEcertificatesResolvers.letsencrypt.acme... + tls.certresolver=letsencrypt.
  7. Add health checksloadBalancer.healthCheck.path=/health.
  8. Connect Prometheus — enable metrics.prometheus and scrape /metrics.
  9. Run 2 instances for HA with shared ACME storage.
  10. Compare with Nginx — if you’re container-native, Traefik’s auto-discovery saves you from config-file + reload cycles.

Common Pitfalls

  • Dashboard exposed without auth — the dashboard shows your entire routing config; secure it with BasicAuth or ForwardAuth.
  • No ACME storage persistenceacme.json must be on a persistent volume; otherwise certs are re-requested on every restart (rate limits).
  • Forgetting traefik.enable=true — without this label, Traefik ignores the container (security: only explicitly-enabled containers are routed).
  • Rule not matching — check the dashboard; common issues: wrong backtick escaping in YAML, missing && between conditions, port in the Host rule.
  • No health check — unhealthy backends keep receiving traffic; add healthCheck.path.
  • Single instance in prod — no HA; run 2+ with shared ACME storage.
  • Port not exposed — the container’s port must be exposed (Docker ports or expose) for Traefik to route to it.
  • Middleware order matters — they run in the listed order; auth before rate-limit is different from rate-limit before auth.

Further Reading

Traefik is the cloud-native edge layer — these PyShine tutorials connect to it:


Traefik’s value is dynamic, auto-discovering, auto-TLS configuration for containerized environments — label a container, and it’s routed; stop it, and the route is gone. No config file, no reload, no certbot. The five stages here — entrypoints, routers, services, middleware, production — cover everything from a single Docker-labeled container to a multi-instance, ACME-secured, ForwardAuth-gated, Prometheus-monitored, HA production edge. The two habits that pay off: always secure the dashboard (it exposes your whole routing config), and persist acme.json (without it, every restart re-requests certs and hits Let’s Encrypt rate limits). Run Traefik with the Docker socket, label a container traefik.http.routers.app.rule=Host(\localhost`)`, and watch the route appear in the dashboard — once you’ve seen dynamic config work, the Nginx reload cycle feels archaic.

Watch PyShine on YouTube

Contents