Traefik’s debug logging is the key to understanding why your requests are misrouted or rejected, revealing the internal decision-making process that typically remains hidden.

Let’s see it in action. Imagine a simple Traefik setup with two services, whoami-a and whoami-b, each running on a different port but exposed under the same hostname traefik.localhost. We want Traefik to route requests based on a path prefix.

Here’s a basic traefik.yml configuration:

# traefik.yml
log:
  level: DEBUG # <-- This is the magic
api:
  dashboard: true
entryPoints:
  web:
    address: ":80"

providers:
  docker:
    exposedByDefault: false

http:
  routers:
    whoami-a-router:
      rule: "Host(`traefik.localhost`) && PathPrefix(`/a`)"
      service: whoami-a-service
      entryPoints:
        - web

    whoami-b-router:
      rule: "Host(`traefik.localhost`) && PathPrefix(`/b`)"
      service: whoami-b-service
      entryPoints:
        - web

  services:
    whoami-a-service:
      loadBalancer:
        servers:
          - url: "http://whoami-a:80"

    whoami-b-service:
      loadBalancer:
        servers:
          - url: "http://whoami-b:80"

And corresponding Docker Compose for the services:

# docker-compose.yml
version: '3.8'

services:
  traefik:
    image: traefik:v2.9
    command:
      - --api.insecure=true # For demo purposes, use proper auth in production
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --entrypoints.web.address=:80
      - --log.level=DEBUG
    ports:
      - "80:80"
      - "8080:8080" # Traefik dashboard
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock

  whoami-a:
    image: traefik/whoami
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.whoami-a-router.rule=Host(`traefik.localhost`) && PathPrefix(`/a`)"
      - "traefik.http.routers.whoami-a-router.entrypoints=web"
      - "traefik.http.services.whoami-a-service.loadbalancer.server.port=80"

  whoami-b:
    image: traefik/whoami
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.whoami-b-router.rule=Host(`traefik.localhost`) && PathPrefix(`/b`)"
      - "traefik.http.routers.whoami-b-router.entrypoints=web"
      - "traefik.http.services.whoami-b-service.loadbalancer.server.port=80"

Now, if you send a request to http://traefik.localhost/a and then http://traefik.localhost/b, you’ll see different responses. But what if something goes wrong?

Enabling DEBUG logging in traefik.yml (or via command-line arguments like --log.level=DEBUG) is the first step. When you do, Traefik’s logs will explode with information. For every incoming request, you’ll see lines detailing:

  1. Request Reception: The raw request details being received by Traefik.
  2. Router Matching: Which routers Traefik is evaluating against the request’s host, path, headers, etc.
  3. Rule Evaluation: The specific outcome of each router’s rule evaluation. This is crucial – it tells you why a router was or wasn’t selected.
  4. Service Selection: Once a router is selected, which service it’s pointing to.
  5. Server Selection: If a service has multiple backend servers, which specific server is chosen.
  6. Request Forwarding: The final request being sent to the selected backend server.

This detailed trace allows you to pinpoint exactly where the routing logic diverged from your expectations. For instance, if http://traefik.localhost/a isn’t hitting whoami-a as expected, the debug logs will show you if the PathPrefix(/a) rule is failing, or if the Host() rule is mismatched, or if the router itself isn’t being discovered by Traefik.

The most surprising thing about Traefik’s debug logging is how it exposes the order of operations and the specific conditions that lead to a match. It’s not just "this rule matched," but "this rule was evaluated, and here’s the exact value of the Host header (traefik.localhost) and the Path (/a) that were used to check it against the defined rule Host(\traefik.localhost`) && PathPrefix(`/a`), and the result was true`." This granular detail is invaluable for debugging complex routing scenarios, middleware application, or even TLS termination issues.

When Traefik evaluates a request, it iterates through all enabled routers. For each router, it checks if the request matches the router’s entry points. If it does, it then evaluates the router’s rule. The rule is a boolean expression. Traefik evaluates these rules lazily, meaning it stops evaluating as soon as it finds a router whose rule evaluates to true and that matches the entry point. This is why the order of your router definitions can matter if their rules are not mutually exclusive or if you’re not being precise enough with your PathPrefix or Host rules. Debug logs will show you precisely which router was evaluated first, second, and so on, and the exact outcome of each rule check.

One detail that often trips people up is how Traefik handles trailing slashes in path-based routing. If you have a router with PathPrefix(/api) and a request comes in for /api/, Traefik will correctly match it because PathPrefix checks for a prefix match, and /api/ starts with /api. However, if you had Path(/api), it would not match /api/ because Path requires an exact match. The debug logs will show the evaluated path and the rule being applied, making such discrepancies immediately obvious.

The next step after mastering debug logging is exploring Traefik’s middleware capabilities, which allow you to modify requests and responses before or after they reach your services.

Want structured learning?

Take the full Traefik course →