Skip to main content

Traefik: a reverse proxy with automatic TLS for Docker

Cloud & DevOps · · 6 min read
Illustration for “Traefik: a reverse proxy with automatic TLS for Docker”

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

Frequently asked questions

Can Traefik proxy traffic to something other than a Docker container, like an external server or IP address?

The labels mechanism described here is built specifically for Docker containers, which Traefik sees on its own through the socket. Routing to an external server or address outside Docker needs a separate static configuration provider instead of container labels, and setting that up turns out to be more manual and less automatic than for local services.

A container is running with labels that look correct, but it's still unreachable from outside — what could be wrong?

Most often one line is missing from the labels, the one that explicitly tells Traefik to notice that particular container — without it the service stays invisible even if every other routing rule is written correctly. It is also worth checking that the flag limiting automatic exposure of containers is not silently blocking this specific service by default.

Do Let's Encrypt certificates issued through Traefik need to be renewed by hand?

No, that is exactly the point of the built-in ACME resolver: Traefik tracks the certificate's expiry itself and requests a new one ahead of time, with no separate cron job and no human involved. The only requirement is that the file storing certificates keeps the correct permissions, otherwise Traefik cannot write the renewed data into it.

Is it worth switching to Traefik if nginx is already running and there are only a few sites?

If there are few sites and configs rarely change, the payoff from automatic container discovery is small, and an extra layer of abstraction plus a separate dashboard instead of familiar nginx logs is extra effort for little gain. Switching pays off once new sites and containers appear often and hand-editing the reverse proxy config each time becomes a noticeable chore.

Was this article helpful?
← Back to Knowledge Base Ask Support