Traefik — Cloud-Native Reverse Proxy with Service Discovery

Practical guide to Traefik v3 — reverse proxy and load balancer with automatic service discovery, middlewares and TLS via Let's Encrypt for Docker.

Traefik is a modern, cloud-native reverse proxy and load balancer that wires up your services for you. Its signature trick is automatic service discovery: through providers for Docker, Kubernetes or file-based configuration, Traefik continuously reads which containers and backends exist and builds its routers and services at runtime – with no reload. TLS is just as effortless: with a certificate resolver, Traefik fetches and renews Let's Encrypt certificates on its own. This guide takes you from the static base configuration through Docker labels and middlewares to the router rules that let you steer traffic with precision.

CLI & Static Configuration

traefik --configFile=<path> — Start Traefik with a specific configuration file.

traefik --configFile=/etc/traefik/traefik.yml

traefik version — Show the Traefik version.

traefik version

traefik healthcheck — Run a health check against a running Traefik instance.

traefik healthcheck --ping

entryPoints: — Define entry points for HTTP and HTTPS traffic (traefik.yml).

entryPoints:
  web:
    address: ":80"
  websecure:
    address: ":443"

api: — Enable the Traefik dashboard. insecure: true exposes it unprotected and belongs in development only – in production, secure the dashboard behind a router and middleware.

api:
  dashboard: true
  insecure: true  # Access at http://localhost:8080

providers.docker: — Enable the Docker provider for auto-discovery of containers. The mounted Docker socket grants root-equivalent access – mount it read-only (:ro) and restrict access tightly.

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false

providers.file: — Enable file-based dynamic configuration with auto-reload.

providers:
  file:
    directory: /etc/traefik/dynamic/
    watch: true

Docker Labels — Routing

traefik.enable=true — Enable Traefik for this container (when exposedByDefault is false).

labels:
  - traefik.enable=true

traefik.http.routers.<name>.rule=Host(`<domain>`) — Route traffic based on the hostname.

labels:
  - traefik.http.routers.myapp.rule=Host(`app.example.com`)

traefik.http.routers.<name>.rule=Host(`<domain>`) && PathPrefix(`<path>`) — Route based on hostname and URL path prefix.

labels:
  - traefik.http.routers.api.rule=Host(`example.com`) && PathPrefix(`/api`)

traefik.http.routers.<name>.entrypoints=<entry> — Specify which entry points the router should listen on.

labels:
  - traefik.http.routers.myapp.entrypoints=websecure

traefik.http.services.<name>.loadbalancer.server.port=<port> — Set the port of the backend service inside the container.

labels:
  - traefik.http.services.myapp.loadbalancer.server.port=8080

traefik.http.routers.<name>.priority=<n> — Set the router priority (higher number = higher priority).

labels:
  - traefik.http.routers.specific.priority=100

Docker Labels — TLS & Let's Encrypt

traefik.http.routers.<name>.tls=true — Enable TLS for a router.

labels:
  - traefik.http.routers.myapp.tls=true

traefik.http.routers.<name>.tls.certresolver=<resolver> — Use a certificate resolver (Let's Encrypt) for automatic TLS.

labels:
  - traefik.http.routers.myapp.tls.certresolver=letsencrypt

certificatesResolvers (HTTP-01): — Configure Let's Encrypt with the HTTP-01 challenge (traefik.yml).

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

certificatesResolvers (DNS-01): — Use the DNS-01 challenge for wildcard certificates.

certificatesResolvers:
  letsencrypt:
    acme:
      email: admin@example.com
      storage: /etc/traefik/acme.json
      dnsChallenge:
        provider: cloudflare

traefik.http.routers.<name>.tls.domains[0].main=<domain> — Set the main domain for the TLS certificate.

labels:
  - traefik.http.routers.myapp.tls.domains[0].main=example.com
  - traefik.http.routers.myapp.tls.domains[0].sans=*.example.com

Docker Labels — Middleware

traefik.http.routers.<name>.middlewares=<middleware> — Attach middleware to a router.

labels:
  - traefik.http.routers.myapp.middlewares=redirect-https

traefik.http.middlewares.<name>.redirectscheme.scheme=https — Redirect HTTP to HTTPS.

labels:
  - traefik.http.middlewares.redirect-https.redirectscheme.scheme=https
  - traefik.http.middlewares.redirect-https.redirectscheme.permanent=true

traefik.http.middlewares.<name>.basicauth.users=<user:hash> — Add HTTP Basic Authentication.

labels:
  - traefik.http.middlewares.auth.basicauth.users=admin:$$apr1$$hash

traefik.http.middlewares.<name>.headers.sslRedirect=true — Force SSL redirect via headers middleware.

labels:
  - traefik.http.middlewares.security.headers.sslRedirect=true

traefik.http.middlewares.<name>.stripprefix.prefixes=<path> — Strip a path prefix before forwarding to the backend.

labels:
  - traefik.http.middlewares.strip-api.stripprefix.prefixes=/api

traefik.http.middlewares.<name>.ratelimit.average=<n> — Rate limiting: allow n requests per second on average.

labels:
  - traefik.http.middlewares.ratelimit.ratelimit.average=100
  - traefik.http.middlewares.ratelimit.ratelimit.burst=50

traefik.http.middlewares.<name>.compress=true — Enable gzip compression for responses.

labels:
  - traefik.http.middlewares.gzip.compress=true

traefik.http.middlewares.<name>.ipallowlist.sourcerange=<cidr> — Restrict access to specific IP ranges.

labels:
  - traefik.http.middlewares.internal.ipallowlist.sourcerange=10.0.0.0/8,172.16.0.0/12

Docker Compose — Full Example

services: (Compose) — Basic Traefik service in Docker Compose. The Docker socket is mounted read-only (:ro) – access to it is root-equivalent.

services:
  traefik:
    image: traefik:v3.3
    command:
      - --providers.docker=true
      - --entrypoints.web.address=:80
    ports:
      - "80:80"
      - "8080:8080"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro

services: (App labels) — Application service with Traefik labels for auto-discovery.

services:
  app:
    image: myapp:latest
    labels:
      - traefik.enable=true
      - traefik.http.routers.app.rule=Host(`app.example.com`)
      - traefik.http.routers.app.tls.certresolver=letsencrypt

networks: — Use a shared external network so Traefik can reach services in other compose files.

# Create first: docker network create traefik-net
networks:
  default:
    name: traefik-net
    external: true

File-Based Dynamic Configuration

http.routers: — Define a router in a YAML dynamic configuration file.

# /etc/traefik/dynamic/myapp.yml
http:
  routers:
    myapp:
      rule: "Host(`example.com`)"
      service: myapp
      tls:
        certResolver: letsencrypt

http.services: — Define a service pointing to a backend server.

http:
  services:
    myapp:
      loadBalancer:
        servers:
          - url: "http://10.0.0.1:8080"
          - url: "http://10.0.0.2:8080"

tls.certificates: — Load custom TLS certificates from files.

tls:
  certificates:
    - certFile: /etc/traefik/certs/example.com.pem
      keyFile: /etc/traefik/certs/example.com-key.pem

Logging & Debugging

log: — Set the log level (DEBUG, INFO, WARN, ERROR).

log:
  level: INFO
  filePath: /var/log/traefik/traefik.log

accessLog: {} — Enable access logging for all requests.

accessLog:
  filePath: /var/log/traefik/access.log
  format: json

ping: {} — Enable the /ping health endpoint.

ping:
  entryPoint: web

metrics: — Enable the Prometheus metrics endpoint.

metrics:
  prometheus:
    entryPoint: metrics
    addEntryPointsLabels: true
    addServicesLabels: true

Useful Router Rules

Host(`<domain>`) — Match requests by hostname.

Host(`example.com`)

Host(`<domain>`) || Host(`<domain2>`) — Match multiple hostnames.

Host(`example.com`) || Host(`www.example.com`)

PathPrefix(`<path>`) — Match requests by URL path prefix.

PathPrefix(`/api`)

Path(`<path>`) — Match an exact URL path.

Path(`/health`)

Headers(`<key>`, `<value>`) — Match requests by header value.

Headers(`X-Custom-Header`, `special-value`)

HostRegexp(`{subdomain:[a-z]+}.example.com`) — Match hostnames with a regular expression.

HostRegexp(`{subdomain:[a-z]+}.example.com`)

ClientIP(`<cidr>`) — Match requests from specific client IP ranges.

ClientIP(`10.0.0.0/8`)

Conclusion

Traefik takes most of the manual proxy upkeep off your hands: new containers show up automatically as routers and services via labels, and Let's Encrypt certificates are issued and renewed without a cron job. Keep a clean separation between what is static (entry points, providers, resolvers) and what arrives dynamically (labels or the file provider) – that keeps your configuration manageable even with many services. And mind the security basics: never expose the dashboard unprotected, and mount the Docker socket read-only.

Further Reading

  • apache – classic web server, also usable as a reverse proxy
  • caddy – web server with automatic HTTPS, similar approach
  • certbot – manage Let's Encrypt certificates manually