Traefik has a security model that’s fundamentally about controlling who can talk to what, and it does that by being the single point of entry for all your services.

Let’s see Traefik in action. Imagine you have a simple web application running in Docker, and you want Traefik to expose it to the outside world.

# docker-compose.yml
version: '3.7'

services:
  whoami:
    image: traefik/whoami
    container_name: whoami
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.whoami.rule=Host(`whoami.localhost`)"
      - "traefik.http.routers.whoami.entrypoints=websecure"
      - "traefik.http.routers.whoami.tls.certresolver=myresolver"
      - "traefik.http.services.whoami.loadbalancer.server.port=80"

  traefik:
    image: traefik:v2.10
    container_name: traefik
    command:
      - --api.insecure=true # For demonstration, disable in production
      - --providers.docker=true
      - --entrypoints.websecure.address=:443
      - --certificatesresolvers.myresolver.acme.tlschallenge=true
      - --certificatesresolvers.myresolver.acme.email=your-email@example.com
      - --certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json
    ports:
      - "443:443"
      - "8080:8080" # For the API dashboard
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./letsencrypt:/letsencrypt

When you run docker-compose up, Traefik starts. It watches Docker for containers with traefik.enable=true labels. When it sees the whoami service, it reads its labels:

  • traefik.http.routers.whoami.rule=Host(\whoami.localhost`): This tells Traefik to route requests with the Hostheaderwhoami.localhost` to this service.
  • traefik.http.routers.whoami.entrypoints=websecure: This means the router should listen on the websecure entrypoint, which we’ve configured for port 443.
  • traefik.http.routers.whoami.tls.certresolver=myresolver: This tells Traefik to obtain and manage TLS certificates for this hostname using the myresolver certificate resolver.
  • traefik.http.services.whoami.loadbalancer.server.port=80: This specifies the actual port on the whoami container that Traefik should forward traffic to.

Traefik then automatically configures itself to:

  1. Listen on port 443 (websecure entrypoint).
  2. Request a TLS certificate for whoami.localhost from Let’s Encrypt (because myresolver is configured with acme.tlschallenge=true and an email).
  3. When a request arrives for whoami.localhost, it terminates the TLS, checks the Host header, and forwards the decrypted HTTP request to the whoami container on port 80.

The API dashboard, accessible at http://localhost:8080, shows these dynamic configurations in real-time. You can see the defined routers, services, and entrypoints, and how they are linked. This dynamic nature is Traefik’s superpower: you don’t restart Traefik when you add or remove a service; you just update its labels or configuration files.

Traefik’s core functionality revolves around EntryPoints, Routers, and Services. An EntryPoint is simply a port Traefik listens on (like 80 for HTTP or 443 for HTTPS). Routers define rules for matching incoming requests (e.g., by hostname, path, header) and specify which EntryPoint they apply to. Services are the backend applications Traefik forwards traffic to. The magic happens when a Router, matched to an EntryPoint, directs traffic to one or more Services. Traefik’s configuration can be static (defined in a file like traefik.yml) or dynamic (loaded from providers like Docker, Kubernetes, Consul, etc.). For production, you’ll typically use a combination, with dynamic configuration driving your application routing and static configuration handling TLS, entrypoints, and basic security.

A common production setup involves Traefik listening on both 80 and 443. All HTTP traffic on port 80 is automatically redirected to HTTPS on port 443. This is achieved by defining two entrypoints and a specific router for the redirect.

# Static configuration example (traefik.yml)
entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: "websecure"
          scheme: "https"
  websecure:
    address: ":443"

certificatesResolvers:
  myresolver:
    acme:
      email: "your-email@example.com"
      storage: "/letsencrypt/acme.json"
      tlsChallenge: true

providers:
  docker:
    exposedByDefault: false
  file:
    directory: /etc/traefik/dynamic_conf/
    watch: true

In this static configuration, Traefik defines web (port 80) and websecure (port 443) entrypoints. The web entrypoint is configured to redirect all incoming traffic to the websecure entrypoint using HTTPS. The certificatesResolvers section configures Let’s Encrypt for automatic TLS certificate management. The providers section tells Traefik to watch Docker for services and also to load additional dynamic configurations from a specified directory.

The most common way to secure Traefik itself is by enabling its API and dashboard, but then restricting access to it. You don’t want your sensitive internal routing details exposed to the public internet. This is often done by enabling basic authentication on the dashboard’s router.

# dynamic_conf/dashboard.yml
http:
  routers:
    dashboard:
      rule: "Host(`traefik.yourdomain.com`) && (PathPrefix(`/dashboard`) || PathPrefix(`/api`))"
      service: "api@internal"
      entryPoints:
        - "websecure"
      middlewares:
        - "auth"
      tls:
        certResolver: "myresolver"

  middlewares:
    auth:
      basicAuth:
        users:
          - "admin:$apr1$h6r1.W2s$mN.vL9.f2b.pX1r7.o.wA0" # Example hashed password for 'user:password'

This configuration defines a router named dashboard that matches requests to traefik.yourdomain.com/dashboard or /api. It uses the internal api@internal service and requires the auth middleware. The auth middleware applies basic HTTP authentication using a bcrypt-hashed password. You can generate these hashes using htpasswd -nb user password and then piping the output through bcrypt. This ensures only authenticated users can access Traefik’s management interface.

The critical security aspect most users overlook is the providers.docker.exposedByDefault setting. If this is true (which is the default in some older configurations), any container with a port published will be automatically exposed by Traefik unless explicitly disabled. Setting exposedByDefault: false in your static Traefik configuration is paramount. Then, you must explicitly enable Traefik for each service using the traefik.enable=true label. This prevents accidental exposure of internal services.

By mastering entrypoints, routers, services, and understanding how dynamic configuration works, you can build a robust and secure ingress layer for your applications.

Want structured learning?

Take the full Traefik course →