Traefik

Cloud-native reverse proxy and load balancer

Reverse Proxy & Gateways ★ 64.9k stars Medium setup MIT

Traefik is a modern reverse proxy and load balancer that automatically discovers services and configures routing. It integrates natively with Docker, Kubernetes, and other orchestrators.

Traefik setup guides & articles

Hands-on coverage of Traefik from the blog.

Key features

  • Automatic service discovery
  • Native Docker and Kubernetes support
  • Let's Encrypt integration
  • Live configuration reload

Pros & cons

Strengths

  • Automatic service discovery
  • Built-in Let's Encrypt
  • First-class Docker support

Trade-offs

  • Configuration learning curve
  • Debugging can be opaque

Traefik replaces

Last reviewed Aug 26, 2026 · 833 words

Traefik earns its place only if you add and remove Docker services often enough that writing proxy config by hand is a chore. Past roughly 5 containers and 1 new one a month, its label-driven routing pays back the learning curve; below that, Caddy gets you to HTTPS in 3 lines and you should stop reading. The catalogue rates Traefik Medium difficulty on 128 MB of RAM, and both numbers are honest: it is light to run and awkward to learn, mostly because there are two configs and the docs do not shout about which is which.

Static and dynamic config are different files, and mixing them is mistake number one

Static configuration (entrypoints, providers, certificate resolvers) is read once at start from traefik.yml or command-line flags. Dynamic configuration (routers, services, middlewares) comes from providers at runtime, and on a Docker host that means container labels. Put a router in traefik.yml and it is silently ignored; put an entrypoint in a label and same result. Almost every "Traefik does nothing" thread on the forums is one of those two.

The static file I start every host from:

entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
  websecure:
    address: ":443"
providers:
  docker:
    exposedByDefault: false
    network: proxy
certificatesResolvers:
  le:
    acme:
      email: [email protected]
      storage: /acme.json
      tlsChallenge: {}

exposedByDefault: false is the line worth underlining. Without it, every container on the host gets a route whether you asked or not, including the database you never meant to publish. With it, nothing is exposed until you say so, per container.

Traefik itself is 15 lines of compose

services:
  traefik:
    image: traefik:v3
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./traefik.yml:/etc/traefik/traefik.yml:ro
      - ./acme.json:/acme.json
      - /var/run/docker.sock:/var/run/docker.sock:ro
    networks:
      - proxy
    restart: unless-stopped

networks:
  proxy:
    external: true

Create the network once with docker network create proxy, and create the certificate store with touch acme.json && chmod 600 acme.json. Traefik refuses to write certificates to a file with looser permissions and logs a single line about it, which is easy to miss.

The socket mount is the uncomfortable part. Read-only access to the Docker socket is still root-equivalent on the host; anyone who compromises Traefik can start containers. The mitigations are a socket-proxy container that filters the Docker API, or running Traefik on a dedicated host. For a home server behind a firewall I accept the risk and keep Traefik updated; for anything with untrusted users I would not.

Four labels expose a service

Every service you want published needs, at minimum:

services:
  whoami:
    image: traefik/whoami
    networks:
      - proxy
    labels:
      - traefik.enable=true
      - traefik.http.routers.whoami.rule=Host(`whoami.example.com`)
      - traefik.http.routers.whoami.entrypoints=websecure
      - traefik.http.routers.whoami.tls.certresolver=le

The container must join the proxy network or Traefik cannot reach it. If the image exposes more than 1 port, add traefik.http.services.whoami.loadbalancer.server.port=8080 (whatever the app listens on) or Traefik picks one and you get a 502 that looks like a certificate problem. Those two conditions, plus a missing enable=true, account for the bulk of the debugging sessions I have sat through.

Debugging is opaque until you turn the lights on

The catalogue's "debugging can be opaque" con is fair. Out of the box Traefik logs almost nothing, and a router that failed to build produces a 404 with no explanation. Two settings fix that: log.level: DEBUG in the static file while you are setting things up, and the dashboard. Enable it with api.dashboard: true, then route it through Traefik itself with a basic-auth middleware rather than opening port 8080 with api.insecure=true, which puts an unauthenticated control panel on your LAN. The dashboard shows every router, its rule, and a red error badge when a label is malformed, which is the fastest way to find a stray backtick.

Certificates deserve their own line. If acme.json stays empty, check the file permissions first, the DNS record second, and whether the port is actually reachable from the internet third. The TLS challenge needs 443 open and the HTTP challenge needs 80; behind CGNAT neither works and you need the DNS challenge with an API token for your DNS provider. The certificates guide covers the DNS route.

What I'd do

On a Docker host with more than a handful of services, Traefik with the static file above, exposedByDefault off, the dashboard behind basic auth, and a Git repo holding every compose file so the labels are versioned. Add the DEBUG log level on day one and remove it on day three. If your host runs 3 containers and will stay that way, take the Caddy versus Traefik decision the other way; the reverse proxy showdown is the longer form of that argument.

Compare Traefik

11 head-to-head comparisons.

Similar reverse proxy & gateways apps