Skip to main content

Traefik: a reverse proxy with automatic TLS for Docker

Cloud & DevOps · 29.09.2026

Traefik is a reverse proxy that discovers Docker containers by itself and issues TLS certificates by itself, with no manual config editing for every new site. Instead of a static file with server blocks like nginx, Traefik reads labels right on the containers and rebuilds routes on the fly. Let's cover setup in Docker Compose, automatic TLS through Let's Encrypt, and common mistakes.

What Traefik is and how it differs from nginx

nginx needs a separate config file for every site and a process reload on any change. Traefik connects to Docker through its socket, sees every running container, and builds routes from their labels automatically — no reload, no separate config file per domain. A new site is added with one line of labels in docker-compose.yml instead of a separate reverse proxy config.

The flip side: Traefik adds one more layer of abstraction on top of the usual HTTP server, and debugging routes means going through its own dashboard rather than the familiar nginx logs.

Installing Traefik in Docker Compose

A basic Traefik service in docker-compose.yml with the Docker provider enabled:

services:
  traefik:
    image: traefik:v3.1
    command:
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./letsencrypt:/letsencrypt
    restart: unless-stopped

The exposedbydefault=false flag is mandatory: without it, Traefik exposes every single container on the server, not just the ones explicitly marked with labels.

Automatic TLS through Let's Encrypt

Traefik requests and renews certificates itself through the ACME protocol, with no cron job and no separate certbot. This is enabled by adding a resolver to the service command:

command:
  - --certificatesresolvers.le.acme.email=admin@example.com
  - --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
  - --certificatesresolvers.le.acme.httpchallenge.entrypoint=web

The acme.json file must have 600 permissions — Let's Encrypt stores private certificate keys in it, and with looser permissions Traefik refuses to use it.

How Traefik finds containers: labels on services

Every container that should get a route is marked with labels right in the service definition:

services:
  app:
    image: myapp:latest
    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=le"
      - "traefik.http.services.app.loadbalancer.server.port=8080"

Traefik picks up the container by itself after docker compose up -d: no need to edit a separate proxy config and restart it.

Several sites on one server

Routing by domain relies on the Host() rule in labels: different containers respond to different domains through the same port 443 of the Traefik service. Each domain gets its own certresolver, and Traefik issues a separate certificate for each Host automatically on the first request.

CriterionTraefikCaddy
Service discoveryAutomatic, through Docker labelsThrough a static Caddyfile
Automatic TLSYes, through an ACME resolverYes, out of the box for any domain
Reload on a new siteNot requiredRequires a config reload

Dashboard and route observability

The built-in dashboard on port 8080 shows the list of routers, services, and middleware in real time — handy for checking that labels were parsed correctly. In production the dashboard is locked down with the same labels mechanism using basic-auth middleware, not left open to the internet.

  • Enable the dashboard with the --api.dashboard=true flag only during debugging
  • Close off the /dashboard/ path with a separate router using an authorization middleware
  • Watch the access log with --accesslog=true when troubleshooting routing issues

Common configuration mistakes

The first mistake is forgetting exposedbydefault=false, which exposes internal containers such as databases to the outside. The second is wrong permissions on acme.json, which silently stops Traefik from saving the certificate and makes it reissue one on every restart, running into Let's Encrypt's rate limits. The third is a missing traefik.enable=true on the target container, leaving the service unreachable even though all other labels are set correctly.

If a server hosts many sites and needs a cluster of several VDS instances, the same Traefik works on top of Docker Swarm too, reading labels from stack mode. For a comparison with a reverse proxy that's simpler to configure, see the article on Caddy Server. If the infrastructure has already moved to full Kubernetes, the equivalent role there is played by ingress-nginx.

Summary: Traefik launch checklist

Traefik removes the need to hand-edit reverse proxy configs for every new container, as long as labels are set up carefully from the start.

  • The exposedbydefault=false flag in the Docker provider configuration
  • 600 permissions on the acme.json file with certificates
  • traefik.enable=true set explicitly on every container that should be visible from outside
  • The dashboard locked behind an authorization middleware, not open to the whole internet
← Back to Knowledge Base Ask Support