Post

Traefik Reverse Proxy & Load Balancer Guide

Traefik is a modern, dynamic reverse proxy and load balancer designed for microservices. This guide covers installation, entrypoints, routers, services, middleware, SSL automation, Docker and Kubernetes integration, and performance tuning.

Traefik Reverse Proxy & Load Balancer Guide

Traefik Web Server Guide

Table of Contents

  1. Introduction to Traefik
  2. Installation
  3. Basic Configuration
  4. Providers Configuration
  5. Routers and Services
  6. Middlewares
  7. TLS/SSL Configuration
  8. Load Balancing
  9. API and Dashboard
  10. Observability
  11. Security Hardening
  12. Troubleshooting
  13. Additional Resources

Introduction to Traefik

Key features:

  • Auto-service discovery: Automatically detects services and generates routes
  • Middleware support: Provides built-in middleware for common needs (authentication, rate limiting, etc.)
  • Multiple providers: Works with Docker, Kubernetes, Marathon, Consul, Etcd, and more
  • Hot reconfiguration: Updates configuration without restarts
  • Metrics exporters: Prometheus, Datadog, StatsD, InfluxDB
  • Let’s Encrypt integration: Automatic HTTPS with certificate generation and renewal
  • WebSocket support: Handles WebSocket connections along with HTTP
  • HTTP/2 and HTTP/3 support: Modern protocol support
  • Tracing system integration: Jaeger, Zipkin, Datadog, Instana

Installation

Binary Installation

1
2
3
4
5
6
7
8
9
10
11
# Download the latest Traefik binary for Linux AMD64
wget https://github.com/traefik/traefik/releases/download/v2.10.7/traefik_v2.10.7_linux_amd64.tar.gz

# Extract the binary
tar -zxvf traefik_v2.10.7_linux_amd64.tar.gz

# Move it to a directory in your PATH
sudo mv traefik /usr/local/bin/

# Check the version
traefik version

Docker Installation

1
2
3
4
5
6
7
8
9
10
# Pull the Traefik image
docker pull traefik:v2.10

# Run Traefik with a simple configuration
docker run -d \
  -p 80:80 \
  -p 8080:8080 \
  -v $PWD/traefik.toml:/etc/traefik/traefik.toml \
  -v /var/run/docker.sock:/var/run/docker.sock \
  traefik:v2.10

Docker Compose Installation

Create a docker-compose.yml file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
version: '3'

services:
  traefik:
    image: traefik:v2.10
    container_name: traefik
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./data/traefik.yml:/traefik.yml:ro
      - ./data/acme.json:/acme.json
    networks:
      - traefik_network

networks:
  traefik_network:
    external: true

Then run:

1
2
3
4
5
6
7
8
9
# Create the external network
docker network create traefik_network

# Create acme.json file for Let's Encrypt certificates
touch data/acme.json
chmod 600 data/acme.json

# Start Traefik
docker-compose up -d

Kubernetes Installation with Helm

1
2
3
4
5
6
7
8
# Add the Traefik Helm repository
helm repo add traefik https://helm.traefik.io/traefik

# Update repositories
helm repo update

# Install Traefik
helm install traefik traefik/traefik

Basic Configuration

Traefik configuration can be defined in different formats: TOML, YAML, command-line flags, or environment variables. This guide will primarily use TOML format for configuration examples.

Static Configuration

The static configuration defines global settings that aren’t expected to change frequently. This is configured at startup and requires a restart if changed.

Create a traefik.toml file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# Global configuration
[global]
  checkNewVersion = true
  sendAnonymousUsage = false

# EntryPoints configuration
[entryPoints]
  [entryPoints.web]
    address = ":80"
    
  [entryPoints.websecure]
    address = ":443"

# Configure log levels
[log]
  level = "INFO"
  format = "common"

# Configure access logs
[accessLog]
  filePath = "/path/to/access.log"
  format = "common"
  bufferingSize = 100

# Configure API and dashboard
[api]
  dashboard = true
  insecure = false  # Set to true only for testing
  
# Configure providers (where Traefik will look for dynamic configuration)
[providers]
  [providers.docker]
    endpoint = "unix:///var/run/docker.sock"
    exposedByDefault = false
  
  [providers.file]
    directory = "/path/to/dynamic/config"
    watch = true

Let’s break down this configuration:

  • global: General settings including version checking
  • entryPoints: Network entry points that Traefik will listen on (ports and protocols)
  • log: Configure logging levels and format
  • accessLog: HTTP request logging configuration
  • api: Settings for the Traefik API and dashboard
  • providers: Sources for dynamic configuration (Docker, file, etc.)

Dynamic Configuration

Dynamic configuration contains settings that can change at runtime (routes, middlewares, etc.). This can be updated without restarting Traefik.

Create a dynamic_conf.toml file:

1
2
3
4
5
6
7
8
9
10
11
12
[http]
  [http.routers]
    [http.routers.my-router]
      rule = "Host(`example.com`)"
      service = "my-service"
      entryPoints = ["web"]
      
  [http.services]
    [http.services.my-service]
      [http.services.my-service.loadBalancer]
        [[http.services.my-service.loadBalancer.servers]]
          url = "http://localhost:8080"

This defines:

  • A router named “my-router” that matches requests for “example.com”
  • A service named “my-service” that forwards requests to localhost:8080

Providers Configuration

Providers are how Traefik discovers the services it needs to create routes for. Here are configurations for common providers:

Docker Provider

1
2
3
4
5
6
7
8
[providers.docker]
  endpoint = "unix:///var/run/docker.sock"
  exposedByDefault = false  # Don't expose containers by default
  network = "traefik_network"  # Only use services on this network
  
  # Define default docker swarm routing rules
  [providers.docker.defaultRule]
    rule = "Host(`.example.com`)"

With this configuration, containers need to opt-in to be exposed by Traefik using labels:

1
2
3
4
5
labels:
  - "traefik.enable=true"
  - "traefik.http.routers.my-container.rule=Host(`my-container.example.com`)"
  - "traefik.http.routers.my-container.entrypoints=websecure"
  - "traefik.http.services.my-container.loadbalancer.server.port=8080"

File Provider

1
2
3
[providers.file]
  directory = "/path/to/dynamic/config"  # Load all .toml files in this directory
  watch = true  # Watch for file changes

Kubernetes Provider

1
2
3
4
[providers.kubernetes]
  # Looks for IngressRoute CRDs
  ingressClass = "traefik-lb"
  namespaces = ["default", "production"]  # Only watch these namespaces

Key-Value Store Providers

1
2
3
4
5
6
7
8
9
# Consul Provider
[providers.consul]
  endpoints = ["localhost:8500"]
  rootKey = "traefik"
  
# Etcd Provider
[providers.etcd]
  endpoints = ["localhost:2379"]
  rootKey = "traefik"

Routers and Services

Traefik routing consists of three main components:

  1. Routers: Define matching rules for incoming requests
  2. Services: Define how to reach the actual services
  3. Middlewares: Define request modifications before they reach the service

Router Configuration

1
2
3
4
5
6
7
8
9
10
11
[http.routers]
  [http.routers.my-api]
    rule = "Host(`api.example.com`) && PathPrefix(`/api`)"
    service = "api-service"
    entryPoints = ["websecure"]
    middlewares = ["rate-limit", "secure-headers"]
    [http.routers.my-api.tls]
      certResolver = "le"
      [[http.routers.my-api.tls.domains]]
        main = "example.com"
        sans = ["*.example.com"]

This router configuration:

  • Matches requests to “api.example.com” with path prefix “/api”
  • Routes traffic to the “api-service”
  • Only listens on the “websecure” entrypoint (port 443)
  • Applies two middlewares: “rate-limit” and “secure-headers”
  • Enables TLS with certificates from Let’s Encrypt

Matching Rules

Traefik supports powerful matching rules combining multiple conditions:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Basic host matching
Host(`example.com`)

# Path matching
PathPrefix(`/api`)

# Method matching
Method(`GET`)

# Header matching
HeadersRegexp(`User-Agent`, `Mozilla/.*`)

# Query parameter matching
Query(`version=v1`)

# Combining rules with AND
Host(`example.com`) && PathPrefix(`/api`)

# Combining rules with OR
Host(`api.example.com`) || (Host(`example.com`) && PathPrefix(`/api`))

Service Configuration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[http.services]
  [http.services.api-service]
    [http.services.api-service.loadBalancer]
      passHostHeader = true
      
      # Health check configuration
      [http.services.api-service.loadBalancer.healthCheck]
        path = "/health"
        interval = "10s"
        timeout = "3s"
      
      # Backend servers
      [[http.services.api-service.loadBalancer.servers]]
        url = "http://10.0.0.10:8080"
      [[http.services.api-service.loadBalancer.servers]]
        url = "http://10.0.0.11:8080"

This service configuration:

  • Defines a load balancer for the “api-service”
  • Passes the original Host header to the backend
  • Configures health checks at the “/health” endpoint
  • Balances traffic between two backend servers

Middlewares

Middlewares modify requests or responses as they pass through Traefik. They can be chained together to create complex request processing pipelines.

Basic Authentication

1
2
3
4
5
6
7
[http.middlewares]
  [http.middlewares.my-auth.basicAuth]
    users = [
      "user:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
      "admin:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"
    ]
    removeHeader = true

To generate the password hash:

1
htpasswd -nb user password

Headers Middleware

1
2
3
4
5
6
7
8
9
10
11
12
13
[http.middlewares]
  [http.middlewares.secure-headers.headers]
    frameDeny = true
    browserXssFilter = true
    contentTypeNosniff = true
    forceSTSHeader = true
    stsIncludeSubdomains = true
    stsPreload = true
    stsSeconds = 31536000
    
    [http.middlewares.secure-headers.headers.customResponseHeaders]
      X-Robots-Tag = "none,noarchive,nosnippet,notranslate,noimageindex"
      Server = ""

Rate Limiting

1
2
3
4
5
6
7
[http.middlewares]
  [http.middlewares.rate-limit.rateLimit]
    average = 100
    burst = 50
    
    [http.middlewares.rate-limit.rateLimit.sourceCriterion]
      ipStrategy = "first_trusted_ip"

IP Whitelisting

1
2
3
[http.middlewares]
  [http.middlewares.ip-whitelist.ipWhiteList]
    sourceRange = ["10.0.0.0/8", "192.168.0.0/16", "172.16.0.0/12"]

URL Rewriting

1
2
3
4
5
6
7
[http.middlewares]
  [http.middlewares.path-strip.stripPrefix]
    prefixes = ["/api"]

  [http.middlewares.path-replace.replacePathRegex]
    regex = "^/api/v1/(.*)"
    replacement = "/v1/$1"

Compression

1
2
3
[http.middlewares]
  [http.middlewares.compress.compress]
    excludedContentTypes = ["text/event-stream"]

Circuit Breaker

1
2
3
[http.middlewares]
  [http.middlewares.circuit-breaker.circuitBreaker]
    expression = "NetworkErrorRatio() > 0.10 || ResponseCodeRatio(500, 600, 0, 600) > 0.25"

Chaining Middlewares

Middlewares can be chained together in a specific order:

1
2
3
4
5
[http.routers]
  [http.routers.my-api]
    rule = "Host(`api.example.com`)"
    service = "api-service"
    middlewares = ["ip-whitelist", "rate-limit", "secure-headers", "compress"]

TLS/SSL Configuration

Traefik makes it easy to configure TLS, including automatic certificate generation with Let’s Encrypt.

Basic TLS Configuration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[entryPoints]
  [entryPoints.websecure]
    address = ":443"
    [entryPoints.websecure.http.tls]
      certResolver = "le"
      
[certificatesResolvers]
  [certificatesResolvers.le]
    [certificatesResolvers.le.acme]
      email = "[email protected]"
      storage = "/acme.json"
      caServer = "https://acme-v02.api.letsencrypt.org/directory"
      
      [certificatesResolvers.le.acme.tlsChallenge]
      # or
      [certificatesResolvers.le.acme.httpChallenge]
        entryPoint = "web"
      # or
      [certificatesResolvers.le.acme.dnsChallenge]
        provider = "cloudflare"
        delayBeforeCheck = 30

To secure specific routers:

1
2
3
4
5
6
7
[http.routers]
  [http.routers.my-secure-router]
    rule = "Host(`secure.example.com`)"
    service = "secure-service"
    entryPoints = ["websecure"]
    [http.routers.my-secure-router.tls]
      certResolver = "le"

Default Certificate

You can provide a default certificate to use when no specific certificate matches:

1
2
3
4
5
6
7
8
[tls]
  [[tls.certificates]]
    certFile = "/path/to/domain.cert"
    keyFile = "/path/to/domain.key"
    stores = ["default"]

  [tls.stores]
    [tls.stores.default]

Client Certificate Authentication (mTLS)

1
2
3
4
[tls]
  [tls.options]
    [tls.options.default]
      clientAuth = {caFiles = ["/path/to/ca.crt"], clientAuthType = "RequireAndVerifyClientCert"}

Then apply these options to a router:

1
2
3
4
5
6
7
[http.routers]
  [http.routers.my-secure-api]
    rule = "Host(`api.example.com`)"
    service = "api-service"
    entryPoints = ["websecure"]
    [http.routers.my-secure-api.tls]
      options = "default"

Advanced TLS Options

1
2
3
4
5
6
7
8
9
10
11
12
[tls.options]
  [tls.options.modern]
    minVersion = "VersionTLS13"
    sniStrict = true
    cipherSuites = [
      "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
      "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
      "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
      "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
      "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
      "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
    ]

Load Balancing

Traefik provides built-in load balancing capabilities for distributing traffic among multiple backend servers.

Basic Load Balancing

1
2
3
4
5
6
7
8
9
[http.services]
  [http.services.my-service]
    [http.services.my-service.loadBalancer]
      [[http.services.my-service.loadBalancer.servers]]
        url = "http://server1:80"
      [[http.services.my-service.loadBalancer.servers]]
        url = "http://server2:80"
      [[http.services.my-service.loadBalancer.servers]]
        url = "http://server3:80"

By default, Traefik uses a round-robin algorithm to distribute requests.

Sticky Sessions

1
2
3
4
5
6
7
8
9
10
11
12
13
[http.services]
  [http.services.my-service]
    [http.services.my-service.loadBalancer]
      sticky = true
      [http.services.my-service.loadBalancer.sticky.cookie]
        name = "MYSESSIONCOOKIE"
        secure = true
        httpOnly = true
        
      [[http.services.my-service.loadBalancer.servers]]
        url = "http://server1:80"
      [[http.services.my-service.loadBalancer.servers]]
        url = "http://server2:80"

Sticky sessions ensure that a client always reaches the same backend server, which is useful for stateful applications.

Server Weights

1
2
3
4
5
6
7
8
9
[http.services]
  [http.services.my-service]
    [http.services.my-service.loadBalancer]
      [[http.services.my-service.loadBalancer.servers]]
        url = "http://server1:80"
        weight = 10
      [[http.services.my-service.loadBalancer.servers]]
        url = "http://server2:80"
        weight = 1

With this configuration, server1 will receive approximately 10 times more traffic than server2.

Health Checks

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[http.services]
  [http.services.my-service]
    [http.services.my-service.loadBalancer]
      [http.services.my-service.loadBalancer.healthCheck]
        path = "/health"
        port = 8080
        interval = "10s"
        timeout = "3s"
        headers = { "X-Custom-Header" = "healthcheck" }
        
      [[http.services.my-service.loadBalancer.servers]]
        url = "http://server1:80"
      [[http.services.my-service.loadBalancer.servers]]
        url = "http://server2:80"

Health checks actively monitor backend servers and automatically remove unhealthy servers from the rotation.

API and Dashboard

Traefik includes an API and a dashboard for monitoring and runtime configuration.

API Configuration

1
2
3
4
[api]
  dashboard = true
  insecure = false  # Don't expose the API/Dashboard without authentication
  debug = false

Securing API/Dashboard with Authentication

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[api]
  dashboard = true
  insecure = false
  
[entryPoints]
  [entryPoints.traefik]
    address = ":8080"
    
[http.routers]
  [http.routers.dashboard]
    rule = "Host(`traefik.example.com`)"
    service = "api@internal"
    entryPoints = ["websecure"]
    middlewares = ["dashboard-auth"]
    [http.routers.dashboard.tls]
      certResolver = "le"
      
[http.middlewares]
  [http.middlewares.dashboard-auth.basicAuth]
    users = [
      "admin:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/"
    ]

This configuration:

  • Enables the dashboard
  • Disables insecure mode (the dashboard isn’t directly exposed)
  • Creates a secured route to access the dashboard via “traefik.example.com”
  • Protects access with basic authentication

Observability

Traefik provides various options for monitoring and observability.

Access Logs

1
2
3
4
5
6
7
8
9
10
11
12
13
[accessLog]
  filePath = "/path/to/access.log"
  format = "json"
  bufferingSize = 100
  
  [accessLog.fields]
    defaultMode = "keep"
    [accessLog.fields.headers]
      defaultMode = "drop"
      [accessLog.fields.headers.names]
        "User-Agent" = "keep"
        "Authorization" = "redact"
        "Content-Type" = "keep"

Metrics

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[metrics]
  [metrics.prometheus]
    entryPoint = "metrics"
    addEntryPointsLabels = true
    addServicesLabels = true
    
  # or
  
  [metrics.datadog]
    address = "localhost:8125"
    pushInterval = "10s"
    
  # or
  
  [metrics.influxdb]
    address = "localhost:8089"
    protocol = "udp"
    pushInterval = "10s"

Tracing

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[tracing]
  serviceName = "traefik"
  spanNameLimit = 100
  
  [tracing.jaeger]
    samplingServerURL = "http://localhost:5778/sampling"
    localAgentHostPort = "localhost:6831"
    
  # or
  
  [tracing.zipkin]
    httpEndpoint = "http://localhost:9411/api/v2/spans"
    sameSpan = true
    id128Bit = true
    sampleRate = 1.0

Security Hardening

Securing your Traefik instance is critical. Here are some best practices:

Remove Insecure Defaults

1
2
3
4
5
6
7
8
9
[api]
  dashboard = true
  insecure = false  # Never use insecure mode in production
  
[global]
  sendAnonymousUsage = false  # Disable anonymous usage reporting
  
[log]
  level = "ERROR"  # Only log errors in production

Restricted Docker Socket Access

If using Docker provider, limit the socket exposure:

1
2
3
4
5
6
7
8
9
version: '3'

services:
  traefik:
    image: traefik:v2.10
    # ... other configuration
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro  # Read-only access
    user: "1000:docker"  # Use non-root user with docker group

Consider using a Docker socket proxy like tecnativa/docker-socket-proxy for even more security.

HTTP Security Headers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[http.middlewares]
  [http.middlewares.security-headers.headers]
    browserXssFilter = true
    contentTypeNosniff = true
    forceSTSHeader = true
    stsIncludeSubdomains = true
    stsPreload = true
    stsSeconds = 31536000
    frameDeny = true
    contentSecurityPolicy = "default-src 'self'; frame-ancestors 'none'"
    
    [http.middlewares.security-headers.headers.customResponseHeaders]
      X-Powered-By = ""
      Server = ""

Apply these headers globally:

1
2
3
4
5
[entryPoints]
  [entryPoints.websecure]
    address = ":443"
    [entryPoints.websecure.http]
      middlewares = ["security-headers@file"]

Limiting TLS Versions

1
2
3
4
5
6
7
[tls.options]
  [tls.options.default]
    minVersion = "VersionTLS12"
    sniStrict = true
    
    [tls.options.modern]
      minVersion = "VersionTLS13"

Running as Non-Root

In Docker:

1
2
FROM traefik:v2.10
USER 1000:1000

Or in docker-compose:

1
2
3
4
services:
  traefik:
    image: traefik:v2.10
    user: "1000:1000"

Securing Sensitive Information

Use environment variables or Docker/Kubernetes secrets for sensitive data:

1
2
3
4
5
[certificatesResolvers.le.acme]
  email = "${ACME_EMAIL}"
  
[http.middlewares.basic-auth.basicAuth]
  usersFile = "/run/secrets/traefik-users"

Network Isolation

Ensure Traefik is in a properly segmented network:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
services:
  traefik:
    networks:
      - public
      - internal
      
  backend:
    networks:
      - internal
      
networks:
  public:
    external: true
  internal:
    internal: true

Rate Limiting

1
2
3
4
[http.middlewares]
  [http.middlewares.rate-limit.rateLimit]
    average = 100
    burst = 50

Apply it globally:

1
2
3
4
5
[entryPoints]
  [entryPoints.web]
    address = ":80"
    [entryPoints.web.http]
      middlewares = ["rate-limit@file"]

Troubleshooting

Common Issues and Solutions

1. Service Not Reachable

Check:

  • Is the backend service running?
  • Are the port numbers correct?
  • Is Traefik in the same network as the service?
  • Are there any firewall issues?

Solution:

1
2
3
# Test connectivity from Traefik container
docker exec -it traefik ping backend-service
docker exec -it traefik wget -O- http://backend-service:8080/health

2. TLS Certificate Issues

Check:

  • Is the ACME challenge properly configured?
  • Do you have the correct permissions on acme.json?
  • Are DNS records correctly set for DNS challenges?

Solution:

1
2
3
4
5
# Check acme.json permissions
chmod 600 acme.json

# Check ACME logs
docker logs traefik | grep -i "acme"

3. Dashboard Not Accessible

Check:

  • Is the API enabled?
  • Is the dashboard enabled?
  • Is there an authentication middleware configured?

Solution:

1
2
3
4
5
6
7
8
9
10
11
12
[api]
  dashboard = true
  
[entryPoints]
  [entryPoints.traefik]
    address = ":8080"
    
[http.routers]
  [http.routers.dashboard]
    rule = "Host(`traefik.example.com`)"
    service = "api@internal"
    middlewares = ["dashboard-auth"]

4. Routing Not Working

Check:

  • Are your routing rules correct?
  • Are you using the correct Host() values?
  • Is the entryPoint correctly configured?

Solution:

1
2
3
4
5
# Enable debug logs
docker run -e TRAEFIK_LOG_LEVEL=DEBUG ...

# Check logs for routing decisions
docker logs traefik | grep -i "rule"

Debugging Techniques

Increase Log Level

1
2
3
[log]
  level = "DEBUG"  # Options: ERROR, WARN, INFO, DEBUG
  format = "json"  # Better for parsing

Use Access Logs

1
2
3
[accessLog]
  filePath = "/path/to/access.log"
  format = "json"

Check Router Status via API

1
curl -s http://localhost:8080/api/http/routers | jq

Test Rules with cURL

1
curl -v -H "Host: example.com" http://localhost

Additional Resources

This post is licensed under CC BY 4.0 by the author.