Traefik Web Server Guide
Table of Contents
- Introduction to Traefik
- Installation
- Basic Configuration
- Providers Configuration
- Routers and Services
- Middlewares
- TLS/SSL Configuration
- Load Balancing
- API and Dashboard
- Observability
- Security Hardening
- Troubleshooting
- 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 checkingentryPoints
: Network entry points that Traefik will listen on (ports and protocols)log
: Configure logging levels and formataccessLog
: HTTP request logging configurationapi
: Settings for the Traefik API and dashboardproviders
: 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:
- Routers: Define matching rules for incoming requests
- Services: Define how to reach the actual services
- 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
|
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.
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"
|
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