Traefik (v3) is an open-source HTTP reverse proxy and load balancer designed for microservices. It automatically discovers services through providers — Docker, Kubernetes, Consul, Nomad, ECS, and more — and configures routing dynamically without restarts. Point Traefik at your infrastructure, label your services, and routing just works.
This guide covers everything you need to install, configure, and operate Traefik in production. It is organized by what you want to accomplish, not by internal architecture. For deep-dive internals, deployment topologies, and exhaustive configuration reference, see the Admin Guide.
1. Quick Start
Get Traefik routing traffic to a sample service in under five minutes using Docker Compose.
1.1 Prerequisites
| Requirement | Minimum Version |
|---|---|
| Docker | 20.10+ |
| Docker Compose | v2.0+ |
| A terminal | bash, zsh, PowerShell, etc. |
1.2 One-File Setup
Create a docker-compose.yml:
services:
traefik:
image: "traefik:v3.7"
command:
- "--api.insecure=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entryPoints.web.address=:80"
ports:
- "80:80"
- "8080:8080"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
whoami:
image: "traefik/whoami"
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami.rule=Host(`whoami.localhost`)"
- "traefik.http.routers.whoami.entrypoints=web"
Start it:
docker compose up -d
Test it:
curl -H "Host: whoami.localhost" http://localhost
You should see a response from the whoami container showing request headers
and the container's hostname.
1.3 What Just Happened
- Traefik started and connected to the Docker daemon via the mounted socket.
- It found the
whoamicontainer becausetraefik.enable=truewas set. - A router was created with the rule
Host(\whoami.localhost`)listening on theweb` entrypoint (port 80). - A service was auto-created pointing to the
whoamicontainer's exposed port. - The dashboard became available at
http://localhost:8080.
Note
The
--api.insecure=trueflag exposes the dashboard without authentication. This is fine for local development but must never be used in production. See Section 3.7 for securing the dashboard.
1.4 Explore the Dashboard
Open http://localhost:8080 in your browser. The dashboard is a single-page
application showing:
| Tab | What It Shows |
|---|---|
| HTTP Routers | All active routing rules and their status |
| HTTP Services | Backend services and their health |
| HTTP Middlewares | Request/response transformations in use |
| TCP Routers | TCP-level routing rules |
| TCP Services | TCP backend services |
| TCP Middlewares | TCP-level middleware rules |
| UDP Routers | UDP routing rules |
| UDP Services | UDP backend services |
| Certificates | TLS certificates managed by Traefik |
The dashboard paginates at 100 items per page by default. Provider icons are shown for each resource, covering Docker, Kubernetes, Consul, ECS, etcd, File, HTTP, Hub, Knative, Nomad, Plugin, Redis, ZooKeeper, and the internal provider.
1.5 Core Concepts at a Glance
Before diving deeper, here are the five building blocks you will work with throughout this guide:
| Concept | What It Does | Configuration Layer |
|---|---|---|
| EntryPoint | Defines where Traefik listens (address + port) | Static |
| Router | Matches requests by rules, routes to services | Dynamic |
| Service | Points to your actual backend servers | Dynamic |
| Middleware | Transforms requests/responses between router and service | Dynamic |
| Provider | Supplies dynamic configuration (Docker, Kubernetes, File, etc.) | Static |
Static configuration is loaded once at startup and requires a restart to change. Dynamic configuration is supplied continuously by providers and updates without restarting Traefik.
2. Installation
2.1 Docker (Recommended)
The official Docker image is the most common deployment method:
docker pull traefik:v3.7
For Docker Compose, see the examples throughout this guide. The image is based
on Alpine Linux and includes the Traefik binary at /usr/local/bin/traefik.
Tip
Always pin a specific version tag (e.g.,
traefik:v3.7) rather than usingtraefik:latest. This prevents unexpected breaking changes when you recreate containers.
2.2 Binary Download
Download the appropriate binary from the GitHub releases page for your platform (Linux, macOS, Windows, FreeBSD). Extract and place it in your PATH:
# Linux amd64 example
wget https://github.com/traefik/traefik/releases/download/v3.7.0/traefik_v3.7.0_linux_amd64.tar.gz
tar xzf traefik_v3.7.0_linux_amd64.tar.gz
sudo mv traefik /usr/local/bin/
traefik version
2.3 Helm Chart (Kubernetes)
helm repo add traefik https://traefik.github.io/charts
helm repo update
helm install traefik traefik/traefik
See Section 4.5 for Kubernetes-specific configuration.
2.4 Package Managers
Traefik is available through various package managers depending on your OS. Check your distribution's repositories for availability. The binary is self-contained with no external dependencies.
2.5 Verifying the Installation
traefik version
This prints the version, build date, and Go version used to compile the binary.
2.6 Healthcheck Command
Traefik includes a built-in healthcheck subcommand that queries the /ping
endpoint:
traefik healthcheck
This is useful for Docker HEALTHCHECK directives and systemd health monitoring.
3. Configuration
Traefik uses two layers of configuration:
| Layer | What It Controls | When It Loads | How to Set It |
|---|---|---|---|
| Static | EntryPoints, providers, certificate resolvers, logging, API | At startup only | File, CLI flags, or environment variables |
| Dynamic | Routers, services, middlewares, TLS options | Continuously (hot-reload) | Providers (Docker labels, Kubernetes CRDs, file, etc.) |
Changing static configuration requires a restart. Dynamic configuration is picked up automatically.
3.1 Static Configuration Methods
You can provide static configuration through three methods. Configuration loaders are applied in this order:
- Deprecation loader (handles deprecated settings)
- File loader (configuration file)
- Flag loader (CLI flags)
- Environment variable loader (env vars — highest priority)
Later loaders override earlier ones.
3.1.1 Configuration File (YAML)
Create traefik.yml:
api:
insecure: true
entryPoints:
web:
address: ":80"
websecure:
address: ":443"
providers:
docker:
exposedByDefault: false
Start Traefik pointing to this file:
traefik --configFile=/etc/traefik/traefik.yml
3.1.2 Configuration File (TOML)
Create traefik.toml:
[api]
insecure = true
[entryPoints]
[entryPoints.web]
address = ":80"
[entryPoints.websecure]
address = ":443"
[providers]
[providers.docker]
exposedByDefault = false
3.1.3 CLI Flags
traefik \
--api.insecure=true \
--entryPoints.web.address=:80 \
--entryPoints.websecure.address=:443 \
--providers.docker=true \
--providers.docker.exposedbydefault=false
3.1.4 Environment Variables
Environment variables use the prefix TRAEFIK_ and replace dots/camelCase
with underscores:
export TRAEFIK_API_INSECURE=true
export TRAEFIK_ENTRYPOINTS_WEB_ADDRESS=:80
export TRAEFIK_ENTRYPOINTS_WEBSECURE_ADDRESS=:443
export TRAEFIK_PROVIDERS_DOCKER=true
export TRAEFIK_PROVIDERS_DOCKER_EXPOSEDBYDEFAULT=false
traefik
3.2 EntryPoints
EntryPoints are the network addresses where Traefik listens for incoming traffic. They are static configuration.
entryPoints:
web:
address: ":80"
websecure:
address: ":443"
custom:
address: ":9000"
Default EntryPoints and asDefault
When at least one entrypoint has asDefault: true, only entrypoints with that
flag are used as defaults for routers that do not specify their entrypoints
explicitly. The internal traefik entrypoint (used for the dashboard/API) is
never included as a default. UDP entrypoints are also excluded from defaults.
entryPoints:
web:
address: ":80"
asDefault: true
websecure:
address: ":443"
asDefault: true
metrics:
address: ":9100"
# Not default — routers won't bind here unless explicitly configured
Important
If you define any entrypoint with
asDefault: true, then all entrypoints WITHOUT that flag will no longer receive traffic from routers that omitentryPoints. This can silently break routing if you addasDefaultto one entrypoint without reviewing existing router configurations.
3.3 Providers
Providers are the source of dynamic configuration. Enable at least one.
3.3.1 Docker Provider
providers:
docker:
exposedByDefault: false
endpoint: "unix:///var/run/docker.sock"
When exposedByDefault is false (recommended), only containers with the
label traefik.enable=true are discovered.
Mount the Docker socket in your Docker Compose file:
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
Important
Mount the Docker socket read-only (
:ro) for security. Traefik only needs to read container metadata, not manage containers. Giving write access to the Docker socket effectively grants root access to the host.
3.3.2 Docker Swarm Provider
For Docker Swarm mode deployments:
providers:
swarm:
exposedByDefault: false
endpoint: "unix:///var/run/docker.sock"
3.3.3 File Provider
Watch a directory of YAML/TOML files for dynamic configuration:
providers:
file:
directory: "/etc/traefik/dynamic/"
watch: true
Or a single file:
providers:
file:
filename: "/etc/traefik/dynamic.yml"
watch: true
The file provider is particularly useful for static backends, custom TLS certificates, and middleware definitions that do not come from a container orchestrator.
3.3.4 HTTP Provider
Poll a remote HTTP endpoint for dynamic configuration:
providers:
http:
endpoint: "https://config.example.com/traefik"
pollInterval: "10s"
3.3.5 Kubernetes Providers
Traefik supports multiple Kubernetes integration methods:
| Provider | Use Case |
|---|---|
Kubernetes CRD (IngressRoute) |
Traefik-native custom resources, full feature access |
| Kubernetes Ingress | Standard Kubernetes Ingress resources |
| Kubernetes Gateway API | Standards-based Gateway API resources |
| Knative | Knative serving integration |
providers:
kubernetesCRD:
allowCrossNamespace: true
# OR
kubernetesIngress: {}
# OR
kubernetesGateway: {}
3.3.6 Consul Catalog
providers:
consulCatalog:
endpoint:
address: "http://consul:8500"
exposedByDefault: false
3.3.7 Nomad
providers:
nomad:
endpoint:
address: "http://nomad:4646"
exposedByDefault: false
3.3.8 Amazon ECS
providers:
ecs:
clusters:
- "my-cluster"
region: "us-east-1"
exposedByDefault: false
3.3.9 KV Store Providers
Traefik supports etcd, Redis, Consul KV, and ZooKeeper as key-value store providers for dynamic configuration:
providers:
redis:
endpoints:
- "redis:6379"
# OR
etcd:
endpoints:
- "etcd:2379"
# OR
consulCatalog:
endpoint:
address: "http://consul:8500"
# OR
zooKeeper:
endpoints:
- "zk:2181"
3.3.10 REST Provider
Push configuration to Traefik via its REST API:
providers:
rest: {}
3.3.11 Tailscale Provider
Traefik can integrate with Tailscale for service discovery and certificate management:
providers:
tailscale: {}
3.4 HTTPS and Automatic Certificates
Traefik can automatically obtain and renew TLS certificates from Let's Encrypt (or any ACME provider) using certificate resolvers.
3.4.1 HTTP Challenge (Port 80 Required)
The HTTP challenge proves domain ownership by serving a token on port 80. This is the simplest ACME setup:
services:
traefik:
image: "traefik:v3.7"
command:
- "--api.insecure=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entryPoints.web.address=:80"
- "--entryPoints.websecure.address=:443"
- "--certificatesresolvers.myresolver.acme.httpchallenge=true"
- "--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web"
- "--certificatesresolvers.myresolver.acme.email=[email removed]"
- "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
ports:
- "80:80"
- "443:443"
- "8080:8080"
volumes:
- "./letsencrypt:/letsencrypt"
- "/var/run/docker.sock:/var/run/docker.sock:ro"
whoami:
image: "traefik/whoami"
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami.rule=Host(`whoami.example.com`)"
- "traefik.http.routers.whoami.entrypoints=websecure"
- "traefik.http.routers.whoami.tls.certresolver=myresolver"
Warning
The
acme.jsonfile stores private keys. Ensure it has restrictive permissions (chmod 600 acme.json) and is persisted via a Docker volume or bind mount. Losing this file means all certificates must be re-issued, which can hit Let's Encrypt rate limits.
3.4.2 TLS-ALPN Challenge (No Port 80 Needed)
The TLS-ALPN challenge proves ownership via a special TLS handshake on port 443. This is useful when port 80 is blocked or unavailable:
command:
- "--certificatesresolvers.myresolver.acme.tlschallenge=true"
- "--certificatesresolvers.myresolver.acme.email=[email removed]"
- "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
Only ports 443 and 8080 need to be published (port 80 is not required).
3.4.3 DNS Challenge (Wildcard Certificates)
The DNS challenge proves ownership via DNS TXT records. This is the only method that supports wildcard certificates and works even when no ports are publicly accessible:
command:
- "--certificatesresolvers.myresolver.acme.dnschallenge=true"
- "--certificatesresolvers.myresolver.acme.dnschallenge.provider=ovh"
- "--certificatesresolvers.myresolver.acme.email=[email removed]"
- "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
DNS provider credentials are passed via environment variables. For example, with OVH:
environment:
- OVH_ENDPOINT=ovh-eu
- OVH_APPLICATION_KEY=xxx
- OVH_APPLICATION_SECRET=xxx
- OVH_CONSUMER_KEY=xxx
Each DNS provider has its own set of required environment variables. Dozens of providers are supported.
3.4.4 Tailscale Certificate Resolver
If you run Traefik within a Tailscale network, Traefik can obtain certificates from Tailscale automatically:
certificatesResolvers:
tailscale: {}
3.4.5 ACME Challenge Comparison
| Challenge | Port Required | Wildcard Support | Use When |
|---|---|---|---|
| HTTP | 80 (public) | No | Port 80 is open, simplest setup |
| TLS-ALPN | 443 (public) | No | Port 80 is blocked |
| DNS | None | Yes | Need wildcards, or no public ports |
| Tailscale | None | N/A | Running inside a Tailscale network |
3.5 HTTP to HTTPS Redirect
A common pattern is to redirect all HTTP traffic to HTTPS.
Option A: Entrypoint-level redirection (recommended)
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
websecure:
address: ":443"
Option B: Middleware-based redirection
# Docker labels on a catch-all router
labels:
- "traefik.http.routers.http-catchall.rule=HostRegexp(`.+`)"
- "traefik.http.routers.http-catchall.entrypoints=web"
- "traefik.http.routers.http-catchall.middlewares=redirect-to-https"
- "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
- "traefik.http.middlewares.redirect-to-https.redirectscheme.permanent=true"
3.6 Logging
3.6.1 Application Logs
log:
level: "INFO" # TRACE, DEBUG, INFO, WARN, ERROR (default), FATAL, PANIC
format: "json" # or console (default)
filePath: "/var/log/traefik/traefik.log"
The default log level is ERROR, which means you see very little output during
normal operation. Set to DEBUG when troubleshooting.
Log rotation is handled automatically via the lumberjack library when writing to a file:
log:
filePath: "/var/log/traefik/traefik.log"
maxSize: 100 # MB before rotation
maxBackups: 5 # rotated files to keep
maxAge: 30 # days to retain
compress: true # gzip rotated files
3.6.2 Access Logs
accessLog:
format: "json" # "common" (extended CLF, default), "genericCLF" (standard CLF), "json"
filePath: "/var/log/traefik/access.log"
| Access Log Format | Description |
|---|---|
common |
Extended Common Log Format (default) — includes extra fields beyond standard CLF |
genericCLF |
Standard Common Log Format |
json |
JSON format — best for log aggregation systems |
Tip
Use
format: "json"for access logs if you plan to ingest them into a log aggregation system (ELK, Loki, Splunk). The JSON format is much easier to parse programmatically than the CLF formats.
3.6.3 OTLP Logging (Experimental)
Send logs via OpenTelemetry Protocol:
experimental:
otlpLogs: true
This requires experimental.otlpLogs=true in static configuration.
3.7 Dashboard Security
The dashboard should never be exposed publicly without authentication. Here are two approaches:
Option A: BasicAuth middleware (simple)
# Dynamic config file
http:
routers:
dashboard:
rule: "Host(`traefik.example.com`)"
service: "api@internal"
entryPoints:
- websecure
middlewares:
- dashboard-auth
tls:
certResolver: myresolver
middlewares:
dashboard-auth:
basicAuth:
users:
- "admin:$apr1$xyz$hashedpassword"
Generate the password hash with:
htpasswd -nb admin your-password
Option B: ForwardAuth (SSO/OAuth)
http:
middlewares:
dashboard-auth:
forwardAuth:
address: "http://authelia:9091/api/verify?rd=https://auth.example.com"
trustForwardHeader: true
authResponseHeaders:
- "Remote-User"
- "Remote-Groups"
3.8 Ping Endpoint
Enable the ping endpoint for load balancer health checks:
ping:
entryPoint: "traefik" # default
terminatingStatusCode: 503 # returned during graceful shutdown
The /ping endpoint returns 200 OK when Traefik is healthy. During graceful
shutdown, it returns the terminatingStatusCode (default 503) so upstream
load balancers can stop sending traffic.
3.9 Version Checking and Telemetry
By default, Traefik checks for new versions every 24 hours (first check 10 minutes after startup) and can send anonymous usage statistics:
global:
checkNewVersion: true # default: true
sendAnonymousUsage: false # opt out of usage reporting
4. Core Usage
4.1 Routing with Rules
Routers match incoming requests using rules and forward them to services. The rule syntax supports these matchers:
| Matcher | Example | Description |
|---|---|---|
Host |
Host(\example.com`)` |
Match by hostname (exact) |
HostRegexp |
HostRegexp(\.+.example.com`)` |
Match hostname by regex |
PathPrefix |
PathPrefix(\/api`)` |
Match URL path prefix |
Path |
Path(\/exact`)` |
Match exact URL path |
Headers |
Headers(\X-Custom`, `value`)` |
Match by request header |
HeadersRegexp |
HeadersRegexp(\Content-Type`, `application/.*`)` |
Match header by regex |
Method |
Method(\GET`, `POST`)` |
Match by HTTP method |
Query |
Query(\debug`, `true`)` |
Match by query parameter |
ClientIP |
ClientIP(\192.168.1.0/24`)` |
Match by client IP/CIDR |
Combine matchers with && (AND) and || (OR):
# Docker label
- "traefik.http.routers.myapp.rule=Host(`app.example.com`) && PathPrefix(`/api`)"
Router Fields
| Field | Purpose |
|---|---|
entryPoints |
Which entrypoints this router listens on |
rule |
The matching expression |
service |
Target service name |
middlewares |
List of middleware names to apply |
priority |
Explicit priority (higher wins) |
tls |
TLS settings (options, certResolver, domains) |
observability |
Per-router access logs, metrics, tracing settings |
Router Priority
When multiple routers match a request, the one with the highest priority wins. If no priority is set explicitly, Traefik calculates it based on rule length (longer rules get higher priority).
# Docker label — explicit priority
- "traefik.http.routers.myapp.priority=100"
4.2 Docker Labels Reference
All dynamic configuration for Docker-based services is set via container labels.
Basic Routing
labels:
- "traefik.enable=true"
- "traefik.http.routers.myapp.rule=Host(`myapp.example.com`)"
- "traefik.http.routers.myapp.entrypoints=websecure"
- "traefik.http.routers.myapp.tls.certresolver=myresolver"
Multiple Routers per Container
labels:
- "traefik.http.routers.myapp-web.rule=Host(`myapp.example.com`)"
- "traefik.http.routers.myapp-web.entrypoints=websecure"
- "traefik.http.routers.myapp-api.rule=Host(`api.myapp.example.com`)"
- "traefik.http.routers.myapp-api.entrypoints=websecure"
Specifying the Container Port
When a container exposes multiple ports, tell Traefik which one to use:
labels:
- "traefik.http.services.myapp.loadbalancer.server.port=8080"
Attaching Middlewares
labels:
- "traefik.http.routers.myapp.middlewares=rate-limit@docker,compress@docker"
Specifying the Docker Network
When the container is connected to multiple networks:
labels:
- "traefik.docker.network=my-network"
4.3 Load Balancing
Traefik distributes traffic across backend servers using configurable strategies.
Load Balancing Strategies
| Strategy | Key | Description |
|---|---|---|
| Weighted Round Robin | wrr |
Default. Distributes requests proportionally by weight |
| Power of Two Choices | p2c |
Picks two random servers, sends to the least loaded |
| Highest Random Weight | hrw |
Consistent hashing — same client tends to reach same server |
| Least Time | leasttime |
Sends to the server with the lowest response time |
Configure via dynamic configuration:
http:
services:
my-service:
loadBalancer:
strategy: "p2c"
servers:
- url: "http://server1:8080"
- url: "http://server2:8080"
- url: "http://server3:8080"
Weighted Round Robin with Weights
http:
services:
my-service:
loadBalancer:
servers:
- url: "http://server1:8080"
weight: 3
- url: "http://server2:8080"
weight: 1
Server 1 receives 75% of traffic, server 2 receives 25%.
4.4 Health Checks
Health checks verify that backend servers are able to handle requests.
Active Health Checks
Traefik periodically sends probe requests to your backends:
http:
services:
my-service:
loadBalancer:
healthCheck:
path: "/health"
interval: "10s"
timeout: "5s"
scheme: "http"
method: "GET"
status: 200
followRedirects: true
| Parameter | Default | Description |
|---|---|---|
path |
— | URL path to probe |
interval |
30s | Time between probes |
unhealthyInterval |
— | Interval when server is unhealthy (overrides interval) |
timeout |
5s | Maximum time to wait for probe response |
scheme |
— | Override scheme (http/https) |
mode |
http |
Protocol mode (http or grpc) |
method |
GET | HTTP method for probe |
status |
— | Expected status code |
followRedirects |
true | Follow redirects during probe |
hostname |
— | Override Host header in probe request |
port |
— | Override port for probe |
headers |
— | Custom headers to include in probe |
Passive Health Checks
Passive checks monitor real traffic for failures without sending extra probe requests:
http:
services:
my-service:
loadBalancer:
healthCheck:
path: "/health"
passiveHealthCheck:
failureWindow: "10s"
maxFailedAttempts: 3
| Parameter | Default | Description |
|---|---|---|
failureWindow |
10s | Time window for counting failures |
maxFailedAttempts |
1 | Failures within the window before marking unhealthy |
4.5 Kubernetes Usage
IngressRoute (CRD — Full Feature Access)
The Traefik CRD provides access to all Traefik features:
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: myapp
spec:
entryPoints:
- websecure
routes:
- match: Host(`myapp.example.com`)
kind: Rule
services:
- name: myapp-svc
port: 80
middlewares:
- name: rate-limit
tls:
certResolver: myresolver
Standard Kubernetes Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls: "true"
spec:
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp-svc
port:
number: 80
Gateway API
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: myapp
spec:
parentRefs:
- name: traefik-gateway
hostnames:
- "myapp.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: myapp-svc
port: 80
4.6 Sticky Sessions
Sticky sessions ensure a client always reaches the same backend server. Traefik implements this via cookies:
http:
services:
my-service:
loadBalancer:
sticky:
cookie:
name: "server_id"
secure: true
httpOnly: true
sameSite: "lax"
maxAge: 3600
path: "/"
| Parameter | Default | Description |
|---|---|---|
name |
auto-generated | Cookie name |
secure |
false | Secure flag (send only over HTTPS) |
httpOnly |
false | HttpOnly flag (no JavaScript access) |
sameSite |
— | none, lax, or strict |
maxAge |
— | Cookie lifetime in seconds |
path |
/ |
Cookie path scope |
domain |
— | Cookie domain scope |
Docker label equivalent:
labels:
- "traefik.http.services.myapp.loadbalancer.sticky.cookie.name=server_id"
- "traefik.http.services.myapp.loadbalancer.sticky.cookie.secure=true"
- "traefik.http.services.myapp.loadbalancer.sticky.cookie.httponly=true"
- "traefik.http.services.myapp.loadbalancer.sticky.cookie.samesite=lax"
4.7 TCP and UDP Routing
Traefik can route raw TCP and UDP traffic in addition to HTTP.
TCP Router
tcp:
routers:
my-tcp:
entryPoints:
- "tcp-ep"
rule: "HostSNI(`db.example.com`)"
service: "my-tcp-service"
tls: {}
services:
my-tcp-service:
loadBalancer:
servers:
- address: "db-server:5432"
UDP Router
udp:
routers:
my-udp:
entryPoints:
- "udp-ep"
service: "my-udp-service"
services:
my-udp-service:
loadBalancer:
servers:
- address: "dns-server:53"
4.8 Service Types
Beyond the standard loadBalancer, Traefik supports advanced service
composition:
Mirroring (Shadow Traffic)
Send a copy of traffic to one or more mirrors for testing or monitoring:
http:
services:
mirrored-service:
mirroring:
service: "main-service"
mirrors:
- name: "shadow-service"
percent: 10
Only responses from the main service are returned to the client. Mirror responses are discarded.
Failover
Route to a fallback service when the primary is unhealthy:
http:
services:
failover-service:
failover:
service: "primary-service"
fallback: "backup-service"
Weighted Round Robin (Service-Level Traffic Splitting)
Distribute traffic across services by weight — commonly used for canary deployments and A/B testing:
http:
services:
canary:
weighted:
services:
- name: "v1"
weight: 90
- name: "v2"
weight: 10
4.9 Response Forwarding
Control how Traefik forwards responses from backends to clients:
http:
services:
my-service:
loadBalancer:
responseForwarding:
flushInterval: "100ms"
The default flushInterval is 100ms. Set to a negative value (e.g., -1ms)
to flush after every write — essential for Server-Sent Events (SSE) and
streaming responses.
4.10 Encoded Path Characters
By default, Traefik rejects requests containing certain encoded characters in the URL path for security:
| Character | Encoded Form | Attack Vector |
|---|---|---|
/ |
%2F |
Path traversal |
\ |
%5C |
Path traversal (Windows) |
| NUL | %00 |
Null byte injection |
; |
%3B |
Parameter injection |
% |
%25 |
Double encoding |
? |
%3F |
Query string injection |
# |
%23 |
Fragment injection |
If your application legitimately needs encoded characters (e.g., REST APIs with
encoded slashes in resource identifiers), individual characters can be allowed
via router-level or middleware-level configuration using the EncodedCharacters
middleware or per-router settings such as allowEncodedSlash and
allowEncodedBackSlash. See the Admin Guide for full configuration reference.
5. Common Tasks
5.1 HTTP Middlewares
Middlewares sit between routers and services, transforming requests and responses. Multiple middlewares can be chained in order.
Here is the complete list of available HTTP middlewares:
| Middleware | Purpose |
|---|---|
| AddPrefix | Prepend a path prefix to requests |
| StripPrefix | Remove a path prefix from requests |
| StripPrefixRegex | Remove a path prefix matching a regex |
| ReplacePath | Replace the entire request path |
| ReplacePathRegex | Replace path components matching a regex |
| Chain | Combine multiple middlewares into a reusable group |
| IPAllowList | Allow only specific IP ranges |
| IPWhiteList | Deprecated — use IPAllowList |
| Headers | Add/modify headers, configure CORS and security headers |
| EncodedCharacters | Control which encoded URL characters are allowed |
| Errors (ErrorPage) | Serve custom error pages |
| RateLimit | Limit request rate per client |
| RedirectRegex | Redirect by regex pattern |
| RedirectScheme | Redirect to a different scheme (e.g., HTTP to HTTPS) |
| BasicAuth | HTTP Basic authentication |
| DigestAuth | HTTP Digest authentication |
| ForwardAuth | Delegate authentication to an external service |
| InFlightReq | Limit simultaneous in-flight requests |
| Buffering | Buffer request/response bodies |
| CircuitBreaker | Stop sending traffic to failing backends |
| Compress | Compress responses (gzip, br, zstd) |
| PassTLSClientCert | Forward client certificate info to backends |
| Retry | Retry failed requests |
| ContentType | Auto-detect Content-Type headers |
| GrpcWeb | Convert gRPC-Web to standard gRPC |
| Plugin | Custom middleware from plugins |
5.1.1 Rate Limiting
Protect services from excessive requests:
http:
middlewares:
rate-limit:
rateLimit:
average: 100 # requests per period
period: "1s" # default: 1s
burst: 50 # max burst above average (default: 1)
The sourceCriterion controls how clients are identified for rate limiting:
http:
middlewares:
rate-limit:
rateLimit:
average: 10
burst: 20
sourceCriterion:
ipStrategy:
depth: 1 # use X-Forwarded-For, skip 1 proxy
Other source criteria options:
requestHeaderName— rate limit by a specific header valuerequestHost— rate limit by the Host header
Distributed Rate Limiting with Redis:
For multi-instance Traefik deployments, rate limit state must be shared. Without Redis, each Traefik instance maintains its own independent counter:
http:
middlewares:
rate-limit:
rateLimit:
average: 100
burst: 50
redis:
endpoints:
- "redis:6379"
password: "secret"
db: 0
poolSize: 10
readTimeout: "3s"
writeTimeout: "3s"
| Redis Parameter | Default | Description |
|---|---|---|
endpoints |
["localhost:6379"] |
Redis server addresses |
tls |
— | TLS configuration for Redis connection |
username |
— | Redis username |
password |
— | Redis password |
db |
0 | Redis database number |
poolSize |
— | Connection pool size |
minIdleConns |
— | Minimum idle connections |
maxActiveConns |
— | Maximum active connections |
readTimeout |
3s | Read timeout |
writeTimeout |
3s | Write timeout |
Docker labels:
labels:
- "traefik.http.middlewares.my-ratelimit.ratelimit.average=100"
- "traefik.http.middlewares.my-ratelimit.ratelimit.burst=50"
5.1.2 Headers, Security Headers, and CORS
The Headers middleware is versatile — it handles custom headers, security headers, and CORS in a single configuration block.
Custom request/response headers:
http:
middlewares:
custom-headers:
headers:
customRequestHeaders:
X-Forwarded-Proto: "https"
X-Custom-Header: "value"
customResponseHeaders:
X-Response-Custom: "value"
Server: "" # empty string removes the header
Security headers:
http:
middlewares:
secure-headers:
headers:
stsSeconds: 31536000
stsIncludeSubdomains: true
stsPreload: true
forceSTSHeader: true
contentTypeNosniff: true
browserXssFilter: true
frameDeny: true
contentSecurityPolicy: "default-src 'self'"
referrerPolicy: "strict-origin-when-cross-origin"
permissionsPolicy: "camera=(), microphone=(), geolocation=()"
allowedHosts:
- "example.com"
- "www.example.com"
| Security Header | Effect |
|---|---|
stsSeconds |
Strict-Transport-Security max-age |
stsIncludeSubdomains |
Include subdomains in HSTS |
stsPreload |
Signal HSTS preload eligibility |
forceSTSHeader |
Send STS even without TLS |
contentTypeNosniff |
X-Content-Type-Options: nosniff |
browserXssFilter |
X-XSS-Protection: 1; mode=block |
frameDeny |
X-Frame-Options: DENY |
customFrameOptionsValue |
Custom X-Frame-Options value (overrides frameDeny) |
contentSecurityPolicy |
Content-Security-Policy header |
referrerPolicy |
Referrer-Policy header |
permissionsPolicy |
Permissions-Policy header |
allowedHosts |
Restrict which Host header values are accepted |
CORS configuration:
http:
middlewares:
cors:
headers:
accessControlAllowMethods:
- "GET"
- "POST"
- "PUT"
- "DELETE"
- "OPTIONS"
accessControlAllowHeaders:
- "Content-Type"
- "Authorization"
accessControlAllowOriginList:
- "https://app.example.com"
- "https://www.example.com"
accessControlAllowOriginListRegex:
- "https://.*\\.example\\.com"
accessControlExposeHeaders:
- "X-Custom-Header"
accessControlMaxAge: 86400
accessControlAllowCredentials: true
addVaryHeader: true
Docker labels for common security headers:
labels:
- "traefik.http.middlewares.secure.headers.stsSeconds=31536000"
- "traefik.http.middlewares.secure.headers.stsIncludeSubdomains=true"
- "traefik.http.middlewares.secure.headers.contentTypeNosniff=true"
- "traefik.http.middlewares.secure.headers.frameDeny=true"
- "traefik.http.middlewares.secure.headers.browserXssFilter=true"
5.1.3 Compression
Compress responses to reduce bandwidth:
http:
middlewares:
compress:
compress:
minResponseBodyBytes: 1024 # default: 1024
encodings: # default: gzip, br, zstd
- "br"
- "gzip"
- "zstd"
excludedContentTypes:
- "text/event-stream"
defaultEncoding: "gzip"
| Parameter | Default | Description |
|---|---|---|
minResponseBodyBytes |
1024 | Minimum body size to compress |
encodings |
["gzip","br","zstd"] |
Supported compression algorithms |
defaultEncoding |
— | Fallback encoding when client does not specify preference |
excludedContentTypes |
— | Content types to never compress |
includedContentTypes |
— | Only compress these content types (exclusive with excluded) |
5.1.4 Authentication
BasicAuth:
http:
middlewares:
auth:
basicAuth:
users:
- "admin:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/"
usersFile: "/etc/traefik/users" # alternative: load from file
realm: "traefik" # default
removeHeader: true # strip Authorization before forwarding
headerField: "X-WebAuth-User" # forward username in this header
Generate hashed passwords:
# Standard htpasswd
htpasswd -nb admin strongpassword
# For Docker labels, double-escape $ signs:
echo $(htpasswd -nb admin strongpassword) | sed -e s/\\$/\\$\\$/g
ForwardAuth (SSO/OAuth integration):
http:
middlewares:
sso:
forwardAuth:
address: "http://authelia:9091/api/verify"
trustForwardHeader: true
authResponseHeaders:
- "Remote-User"
- "Remote-Groups"
- "Remote-Email"
authResponseHeadersRegex: "^Remote-"
authRequestHeaders:
- "Accept"
- "Cookie"
| ForwardAuth Parameter | Default | Description |
|---|---|---|
address |
— | URL of the auth service (required) |
trustForwardHeader |
false | Trust existing X-Forwarded-* headers |
authResponseHeaders |
— | Headers to copy from auth response to request |
authResponseHeadersRegex |
— | Regex to match auth response headers to copy |
authRequestHeaders |
— | Headers to forward to auth service |
maxResponseBodySize |
— | Max auth response body to read |
addAuthCookiesToResponse |
— | Cookies from auth response to add to client response |
headerField |
— | Header to store auth user info |
forwardBody |
false | Forward request body to auth service |
maxBodySize |
-1 (unlimited) | Max body size to forward to auth service |
preserveLocationHeader |
false | Preserve Location header from auth response |
preserveRequestMethod |
false | Use original HTTP method for auth request |
authSigninURL |
— | Redirect URL when authentication fails |
DigestAuth:
http:
middlewares:
auth:
digestAuth:
users:
- "admin:traefik:hashed"
5.1.5 IP Allowlisting
Restrict access to specific IP ranges:
http:
middlewares:
ip-allow:
ipAllowList:
sourceRange:
- "10.0.0.0/8"
- "172.16.0.0/12"
- "192.168.0.0/16"
rejectStatusCode: 403 # default: 403
Use ipStrategy when behind other proxies to extract the real client IP:
http:
middlewares:
ip-allow:
ipAllowList:
sourceRange:
- "203.0.113.0/24"
ipStrategy:
depth: 1 # skip 1 proxy in X-Forwarded-For
Note
The
ipWhiteListmiddleware is deprecated. UseipAllowListinstead. Existing configurations usingipWhiteListwill continue to work but should be migrated.
5.1.6 Path Manipulation
AddPrefix — prepend a path:
http:
middlewares:
add-api:
addPrefix:
prefix: "/api/v1"
StripPrefix — remove a path prefix:
http:
middlewares:
strip-api:
stripPrefix:
prefixes:
- "/api"
StripPrefixRegex — remove by regex:
http:
middlewares:
strip-regex:
stripPrefixRegex:
regex:
- "/api/v[0-9]+"
ReplacePath — replace the entire path:
http:
middlewares:
replace:
replacePath:
path: "/new-path"
ReplacePathRegex — replace by regex:
http:
middlewares:
replace-regex:
replacePathRegex:
regex: "^/old/(.*)"
replacement: "/new/$1"
5.1.7 Circuit Breaker
Protect services from cascading failures:
http:
middlewares:
circuit-breaker:
circuitBreaker:
expression: "ResponseCodeRatio(500, 600, 0, 600) > 0.30"
checkPeriod: "100ms" # default
fallbackDuration: "10s" # default — time to stay open after tripping
recoveryDuration: "10s" # default — time in half-open state
responseCode: 503 # default — code returned when open
The circuit breaker transitions through three states:
| State | Behavior |
|---|---|
| Closed | Normal operation — all requests forwarded, expression evaluated periodically |
| Open | Breaker tripped — all requests immediately get responseCode (503) |
| Half-Open | After fallbackDuration, let some requests through to test recovery |
If requests succeed during half-open, the breaker returns to closed after
recoveryDuration. If they fail, it goes back to open.
5.1.8 Retry
Automatically retry failed requests:
http:
middlewares:
retry:
retry:
attempts: 3
initialInterval: "100ms"
maxRequestBodyBytes: 2097152 # 2MB default (2 * 1024 * 1024)
Caution
Be careful with retries on non-idempotent requests (POST, PUT, DELETE). Retrying these can cause duplicate operations. Use retries primarily for GET requests or idempotent endpoints. The
maxRequestBodyByteslimit (default 2MB) prevents unbounded memory usage when buffering request bodies for retries.
5.1.9 Custom Error Pages
Show custom error pages for specific status codes:
http:
middlewares:
error-pages:
errors:
status:
- "500-599"
service: "error-service"
query: "/{status}.html"
The query field supports these template variables:
| Variable | Description |
|---|---|
{status} |
The HTTP status code returned |
{originalStatus} |
The original status from the backend |
{url} |
The original request URL |
Status rewrites let you change the status code sent to the error service:
http:
middlewares:
error-pages:
errors:
status:
- "500-599"
statusRewrites:
"500": 200
"503": 200
service: "error-service"
query: "/{status}.html"
5.1.10 In-Flight Request Limiting
Limit the number of simultaneous requests being processed:
http:
middlewares:
inflight:
inFlightReq:
amount: 100
sourceCriterion:
requestHost: true
5.1.11 Buffering
Buffer request and response bodies:
http:
middlewares:
buffer:
buffering:
maxRequestBodyBytes: 10485760 # 10MB
maxResponseBodyBytes: 10485760
retryExpression: "IsNetworkError() && Attempts() < 2"
5.1.12 Redirect by Regex
http:
middlewares:
redirect:
redirectRegex:
regex: "^https://old.example.com/(.*)"
replacement: "https://new.example.com/${1}"
permanent: true
5.1.13 Middleware Chains
Combine multiple middlewares into a reusable chain:
http:
middlewares:
secured:
chain:
middlewares:
- rate-limit
- ip-allow
- secure-headers
- auth
routers:
myapp:
rule: "Host(`app.example.com`)"
middlewares:
- secured
service: myapp
Docker label for middleware chain:
labels:
- "traefik.http.routers.myapp.middlewares=rate-limit@docker,compress@docker,auth@docker"
5.1.14 gRPC-Web
Convert gRPC-Web requests to standard gRPC:
http:
middlewares:
grpcweb:
grpcWeb: {}
5.1.15 Pass TLS Client Certificate
Forward TLS client certificate information to backend services:
http:
middlewares:
pass-cert:
passTLSClientCert:
pem: true
5.1.16 Content-Type Auto-Detection
http:
middlewares:
autodetect:
contentType: {}
5.2 TCP Middlewares
TCP-level middlewares have a smaller feature set:
TCP IP Allowlisting:
tcp:
middlewares:
tcp-ipallow:
ipAllowList:
sourceRange:
- "10.0.0.0/8"
TCP In-Flight Connection Limiting:
tcp:
middlewares:
tcp-inflight:
inFlightConn:
amount: 100
5.3 TLS Configuration
TLS Versions
| Constant | TLS Version |
|---|---|
VersionTLS10 |
TLS 1.0 |
VersionTLS11 |
TLS 1.1 |
VersionTLS12 |
TLS 1.2 |
VersionTLS13 |
TLS 1.3 |
tls:
options:
default:
minVersion: "VersionTLS12"
maxVersion: "VersionTLS13"
Cipher Suites
Custom cipher suites can be specified for TLS 1.2 connections:
tls:
options:
default:
cipherSuites:
- "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
- "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
TLS 1.3 cipher suites are fixed by the Go standard library and cannot be customized.
Curve Preferences
tls:
options:
default:
curvePreferences:
- "X25519"
- "CurveP256"
- "CurveP384"
- "CurveP521"
- "X25519MLKEM768"
X25519MLKEM768 is a post-quantum hybrid key exchange mechanism combining
X25519 with ML-KEM-768 for forward-looking protection against quantum
computing threats.
OCSP Stapling
Traefik supports OCSP stapling automatically. When TLS certificates include OCSP responder URLs, Traefik fetches and caches OCSP responses, stapling them during TLS handshakes. No configuration is needed.
Manual Certificate Loading
tls:
certificates:
- certFile: "/certs/example.com.crt"
keyFile: "/certs/example.com.key"
- certFile: "/certs/wildcard.example.com.crt"
keyFile: "/certs/wildcard.example.com.key"
5.4 ServersTransport (Backend TLS)
Configure how Traefik connects to backend services over TLS:
http:
serversTransports:
my-transport:
serverName: "backend.example.com"
insecureSkipVerify: false
rootCAs:
- "/certs/ca.pem"
certificates:
- certFile: "/certs/client.pem"
keyFile: "/certs/client-key.pem"
maxIdleConnsPerHost: 200
forwardingTimeouts:
dialTimeout: "30s"
responseHeaderTimeout: "0s"
idleConnTimeout: "90s"
readIdleTimeout: "0s"
pingTimeout: "15s"
disableHTTP2: false
| Parameter | Default | Description |
|---|---|---|
serverName |
— | Server name for TLS verification |
insecureSkipVerify |
false | Skip TLS certificate verification |
rootCAs |
— | Custom CA certificates for verification |
certificates |
— | Client certificates for mTLS |
maxIdleConnsPerHost |
— | Max idle connections per backend host |
dialTimeout |
30s | Timeout for establishing connection |
responseHeaderTimeout |
— | Timeout waiting for response headers |
idleConnTimeout |
90s | Timeout for idle connections |
readIdleTimeout |
— | Timeout for idle HTTP/2 connections |
pingTimeout |
15s | Timeout for HTTP/2 pings |
disableHTTP2 |
false | Force HTTP/1.1 to backends |
peerCertURI |
— | Expected peer certificate URI |
SPIFFE Integration
For service mesh environments using SPIFFE:
http:
serversTransports:
spiffe-transport:
spiffe:
ids:
- "spiffe://trust-domain/workload"
trustDomain: "trust-domain"
5.5 Plugins
Traefik supports plugins to extend its functionality with custom middlewares, providers, and more.
Remote Plugins (from Traefik Plugin Registry)
experimental:
plugins:
my-plugin:
moduleName: "github.com/author/my-plugin"
version: "v1.0.0"
Remote plugins are powered by the Yaegi Go interpreter — they are interpreted Go code, not compiled binaries.
Local Plugins
experimental:
localPlugins:
my-local-plugin:
moduleName: "github.com/author/my-local-plugin"
Local plugins are loaded from ./plugins-local/ relative to Traefik's working
directory.
WebAssembly (WASI) Plugins
Traefik also supports WASM-based plugins for portable, sandboxed middleware execution. WASI plugins offer stronger isolation than Yaegi plugins.
Plugin Storage and Failure Handling
Plugin storage is located at ./plugins-storage/ by default. Control startup
behavior when plugins fail:
experimental:
abortOnPluginFailure: true # Traefik won't start if any plugin fails to load
5.6 Observability
5.6.1 Prometheus Metrics
metrics:
prometheus: {}
Metrics are exposed on the Traefik entrypoint (default port 8080) at /metrics.
5.6.2 Datadog
metrics:
datadog:
address: "127.0.0.1:8125"
pushInterval: "10s"
5.6.3 StatsD
metrics:
statsd:
address: "127.0.0.1:8125"
pushInterval: "10s"
5.6.4 InfluxDB2
metrics:
influxDB2:
address: "http://influxdb:8086"
token: "my-token"
org: "my-org"
bucket: "traefik"
pushInterval: "10s"
5.6.5 OpenTelemetry (OTLP)
metrics:
otlp:
pushInterval: "10s"
tracing:
otlp: {}
5.6.6 Per-Router Observability
Individual routers can opt in or out of access logs, metrics, and tracing:
http:
routers:
myapp:
rule: "Host(`app.example.com`)"
service: myapp
observability:
accessLogs: true
metrics: true
tracing: true
traceVerbosity: "recommended"
5.7 Systemd Integration
When running as a systemd service, Traefik integrates with systemd's notification protocol:
- Sends
READY=1viasd_notifywhen startup is complete - Sends
WATCHDOG=1at half the configured watchdog interval (by querying its own/pingendpoint)
Example systemd unit:
[Unit]
Description=Traefik Proxy
After=network-online.target
Wants=network-online.target
[Service]
Type=notify
ExecStart=/usr/local/bin/traefik --configFile=/etc/traefik/traefik.yml
WatchdogSec=30s
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
5.8 Complete Docker Compose Examples
These are complete, ready-to-use Docker Compose configurations for common deployment patterns.
HTTPS with HTTP Challenge (Most Common)
services:
traefik:
image: "traefik:v3.7"
command:
- "--api.insecure=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entryPoints.web.address=:80"
- "--entryPoints.websecure.address=:443"
- "--certificatesresolvers.myresolver.acme.httpchallenge=true"
- "--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web"
- "--certificatesresolvers.myresolver.acme.email=[email removed]"
- "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
ports:
- "80:80"
- "443:443"
- "8080:8080"
volumes:
- "./letsencrypt:/letsencrypt"
- "/var/run/docker.sock:/var/run/docker.sock:ro"
whoami:
image: "traefik/whoami"
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami.rule=Host(`whoami.example.com`)"
- "traefik.http.routers.whoami.entrypoints=websecure"
- "traefik.http.routers.whoami.tls.certresolver=myresolver"
HTTPS with TLS-ALPN Challenge (No Port 80)
services:
traefik:
image: "traefik:v3.7"
command:
- "--api.insecure=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entryPoints.websecure.address=:443"
- "--certificatesresolvers.myresolver.acme.tlschallenge=true"
- "--certificatesresolvers.myresolver.acme.email=[email removed]"
- "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
ports:
- "443:443"
- "8080:8080"
volumes:
- "./letsencrypt:/letsencrypt"
- "/var/run/docker.sock:/var/run/docker.sock:ro"
whoami:
image: "traefik/whoami"
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami.rule=Host(`whoami.example.com`)"
- "traefik.http.routers.whoami.entrypoints=websecure"
- "traefik.http.routers.whoami.tls.certresolver=myresolver"
HTTPS with DNS Challenge (Wildcard Certificates)
services:
traefik:
image: "traefik:v3.7"
command:
- "--api.insecure=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entryPoints.websecure.address=:443"
- "--certificatesresolvers.myresolver.acme.dnschallenge=true"
- "--certificatesresolvers.myresolver.acme.dnschallenge.provider=ovh"
- "--certificatesresolvers.myresolver.acme.email=[email removed]"
- "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
ports:
- "443:443"
- "8080:8080"
environment:
- OVH_ENDPOINT=ovh-eu
- OVH_APPLICATION_KEY=xxx
- OVH_APPLICATION_SECRET=xxx
- OVH_CONSUMER_KEY=xxx
volumes:
- "./letsencrypt:/letsencrypt"
- "/var/run/docker.sock:/var/run/docker.sock:ro"
whoami:
image: "traefik/whoami"
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami.rule=Host(`whoami.example.com`)"
- "traefik.http.routers.whoami.entrypoints=websecure"
- "traefik.http.routers.whoami.tls.certresolver=myresolver"
6. Troubleshooting
6.1 Startup and Configuration Issues
T1. Traefik starts but no routes are active
Symptom: Dashboard shows zero routers or services.
Causes and fixes:
- Verify a provider is configured. Without at least one provider, there is no source of dynamic configuration.
- For Docker: ensure
providers.docker=trueand the Docker socket is mounted. - For Docker: if
exposedByDefault=false, check that containers havetraefik.enable=true. - Check Traefik logs at
DEBUGlevel:--log.level=DEBUG.
T2. "Unable to find the IP address" for a container
Symptom: Error in logs about not finding a container's IP.
Fix: Ensure Traefik and the target container share a Docker network. By default,
containers on different networks cannot communicate. Add both to the same
network or specify the network via label:
traefik.docker.network=my-network.
T3. Certificate resolver fails silently
Symptom: HTTPS connections fail with self-signed or missing certificate.
Fixes:
- Check that the ACME email is set and valid.
- For HTTP challenge: port 80 must be publicly reachable.
- For TLS-ALPN challenge: port 443 must be publicly reachable.
- For DNS challenge: verify environment variables for your DNS provider.
- Check
acme.jsonpermissions — must be600. - Look for errors in logs at
DEBUGlevel.
T4. Dashboard returns 404
Symptom: http://localhost:8080 shows nothing.
Fixes:
- Ensure
--api.insecure=trueis set (for local dev) or a proper router pointing toapi@internalexists. - Verify port 8080 is published in Docker Compose.
- The dashboard listens on the
traefikentrypoint by default.
T5. Static configuration changes not taking effect
Symptom: You changed traefik.yml but nothing happened.
Fix: Static configuration is only loaded at startup. Restart Traefik after any changes to entrypoints, providers, or certificate resolvers. Dynamic configuration (routers, services, middlewares) reloads automatically.
T6. "port is missing" error on startup
Symptom: Traefik logs an error about a missing or invalid port.
Fix: EntryPoint addresses must include a port number. Use the format
:80, :443, or 0.0.0.0:80. An address without a colon and port will fail.
T7. Environment variables ignored
Symptom: Settings passed via environment variables are not applied.
Fix: Traefik environment variables must use the prefix TRAEFIK_ with
underscores replacing dots and uppercase for all parts. For example,
--entryPoints.web.address becomes TRAEFIK_ENTRYPOINTS_WEB_ADDRESS.
6.2 Routing Issues
T8. Wrong service receives traffic
Symptom: Requests go to the wrong backend.
Fixes:
- Check router priorities. Longer rules get higher auto-priority, but explicit
priorityvalues override this. Two routers with the same priority on overlapping rules cause unpredictable behavior. - Use the dashboard to inspect which routers are active and their priorities.
- Remember that if multiple routers match, the highest priority wins.
T9. 404 on a route that should work
Symptom: Router exists in dashboard but returns 404.
Fixes:
- Verify the
Hostheader matches the rule. Usecurl -H "Host: example.com". - Check that the service has healthy servers (dashboard shows green status).
- Ensure the entrypoint in the router matches the port you are hitting.
- Confirm the container is running and the correct port is exposed.
T10. 502 Bad Gateway
Symptom: Router matches but backend returns 502.
Fixes:
- The backend service is down or unreachable from Traefik's network.
- Wrong port specified in
loadbalancer.server.port. - Backend uses HTTPS but Traefik connects via HTTP (or vice versa).
- For Kubernetes with
PathPrefix: some frameworks (e.g., ASP.NET withUsePathBase) may not handle the forwarded path correctly (GitHub issue #12997).
T11. 400 Bad Request on POST requests via Gateway API
Symptom: GET works but POST returns 400 through Gateway API HTTPRoute.
This is a known issue (GitHub issue #12345) where certain POST requests through Gateway API HTTPRoute resources return 400 Bad Request. Check if the issue has been resolved in your Traefik version.
T12. Encoded characters rejected (403 or 400)
Symptom: URLs containing %2F, %5C, or other encoded characters are blocked.
Fix: Traefik rejects encoded path characters by default for security (%2F,
%5C, %00, %3B, %25, %3F, %23). If your application requires them,
enable the specific characters you need via the EncodedCharacters middleware
or per-router settings.
T13. Path rewriting stopped working after upgrade
Symptom: Path rewriting that previously worked now fails.
This is a known regression (GitHub issue #12982) introduced after #12905. If path rewriting breaks after an upgrade, check the release notes and consider pinning to a version before the regression.
6.3 TLS and Certificate Issues
T14. TLS handshake failures with long ClientHello
Symptom: TCP connections terminated during TLS handshake, especially with large or fragmented ClientHello messages.
This is a known issue (GitHub issue #12871). Long or fragmented TLS ClientHello messages can cause Traefik to terminate the TCP connection. Workaround: check if the client is sending an unusually large ClientHello (e.g., with many cipher suites or extensions) and reduce it if possible.
T15. "acme: error 429" — rate limited by Let's Encrypt
Symptom: Certificate issuance fails with rate limit error.
Fix: Let's Encrypt enforces rate limits (50 certificates per registered domain per week). Use the staging server for testing:
command:
- "--certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory"
Tip
Always use the Let's Encrypt staging server during initial setup and testing. Switch to production only once everything works correctly. Staging certificates will show browser warnings but prove your configuration is correct.
T16. Mixed HTTP/HTTPS causes redirect loop
Symptom: Browser shows "too many redirects".
Fix: Ensure the HTTPS router and the HTTP-to-HTTPS redirect router use
different entrypoints. The redirect should be on web (port 80) and the actual
router on websecure (port 443). If both listen on the same entrypoint, the
redirect triggers infinitely.
T17. Self-signed certificate served instead of Let's Encrypt
Symptom: Browser shows certificate warning even though ACME is configured.
Fixes:
- Verify the router has
tls.certresolver=myresolverset. - Check that the domain in the
Hostrule matches the requested domain exactly. - Inspect
acme.json— if it is empty or missing the domain, the challenge failed. Check logs atDEBUGlevel. - Ensure the required port (80 for HTTP challenge, 443 for TLS-ALPN) is publicly accessible and not blocked by a firewall.
6.4 Middleware Issues
T18. Rate limiter not working across multiple Traefik instances
Symptom: Rate limits are per-instance, not global.
Fix: Configure the Redis backend for the rate limiter. Without Redis, each Traefik instance maintains its own counter independently. See Section 5.1.1 for Redis configuration.
T19. BasicAuth prompts repeatedly
Symptom: Browser asks for credentials on every request.
Fixes:
- Ensure the password hash format matches what
htpasswdproduces. - In Docker labels, escape
$signs by doubling them:$$. A single$is interpreted as a variable by Docker Compose. - Verify
removeHeaderis set correctly — iftrue, the Authorization header won't reach the backend, which is usually desired. - Check that the
realmvalue is consistent across requests.
T20. ForwardAuth returns 401 but auth service works directly
Symptom: Auth service responds 200 when called directly but ForwardAuth fails.
Fixes:
- Check that
trustForwardHeaderis set appropriately for your proxy chain. - Verify the
authRequestHeadersincludes all headers the auth service needs (e.g.,Cookie,Authorization). - Check network connectivity between Traefik and the auth service container.
- Inspect what headers Traefik actually sends to the auth service using
DEBUGlogging.
T21. Compress middleware not compressing
Symptom: Responses are not compressed despite the middleware being active.
Fixes:
- Response body must be at least 1024 bytes (default
minResponseBodyBytes). - The client must send an
Accept-Encodingheader with a supported encoding. - Check
excludedContentTypes— some content types may be excluded. - Streaming responses (
text/event-stream) are typically excluded. - Verify the middleware is actually attached to the router (check dashboard).
T22. Circuit breaker trips unexpectedly
Symptom: Service returns 503 even though backends seem healthy.
Fixes:
- Review the expression.
ResponseCodeRatio(500, 600, 0, 600) > 0.30means the circuit trips when more than 30% of responses are 5xx. - The
checkPeriod(default 100ms) determines how often the expression is evaluated. During short bursts of errors, a quick check period can trip the breaker prematurely. - After tripping, the breaker stays open for
fallbackDuration(default 10s) before entering half-open state.
6.5 Backend and Service Issues
T23. Health check marks all servers unhealthy
Symptom: Dashboard shows all servers as unhealthy, traffic returns 503.
Fixes:
- Verify the health check
pathreturns a 200 status on the backend directly. - Check that the health check
portmatches the backend's actual port. - If using
mode: grpc, ensure the backend implements the gRPC health checking protocol. - Increase the
timeoutif backends are slow to respond (default is 5s). - Verify network connectivity between Traefik and the backend on the health check port.
T24. Sticky sessions not working
Symptom: Requests land on different backends despite sticky cookie configuration.
Fixes:
- Check that the sticky cookie is being set (inspect response
Set-Cookieheaders). - Ensure
secure: trueis not set when testing over plain HTTP — the browser will not send the cookie. - Verify
sameSitesetting is compatible with your client's origin. - Check that the cookie
pathmatches the request path.
T25. Response body corruption with mirroring + retry
Symptom: Intermittent garbled or incomplete response bodies when using mirroring and retry middlewares together.
This is a known data race issue (GitHub issue #12988). Avoid combining mirroring with retry on the same router until a fix is released.
Warning
Using the retry middleware together with traffic mirroring can cause response body corruption due to a data race (GitHub issue #12988). If you need both features, apply them on separate routers or wait for a fix.
T26. ServiceName shows RouterName in access logs
Symptom: The ServiceName field in access logs or metrics incorrectly reports
the router name instead of the actual service name.
This is a known issue (GitHub issue #12951) affecting IngressRoute resources. The service name in logs may show the router name. This is cosmetic and does not affect routing behavior.
6.6 Kubernetes-Specific Issues
T27. Traefik stops serving ingresses when kube-apiserver is down
Symptom: Existing ingress-nginx routes stop working when the Kubernetes API server becomes unreachable.
This is a known issue (GitHub issue #12945). When the kube-apiserver goes down, Traefik may lose its cached configuration. Consider running with a file provider as a fallback for critical routes.
T28. Unnecessary BackendTLSPolicy status updates
Symptom: Kubernetes logs show repeated "object has been modified" errors related to BackendTLSPolicy.
This is a known issue (GitHub issue #12511) where Traefik unnecessarily updates BackendTLSPolicy status, causing conflict errors with the API server. This is cosmetic but generates noisy logs.
T29. Nginx Session Affinity annotations not working
Symptom: Session affinity annotations from ingress-nginx are ignored.
This is a known issue (GitHub issue #12998). Traefik does not recognize nginx-specific annotations for session affinity. Use Traefik-native sticky session configuration instead (see Section 4.6).
6.7 Performance and Operational Issues
T30. High memory usage
Symptom: Traefik process consumes excessive memory.
Fixes:
- Reduce the number of watched containers/services if possible.
- Disable providers you don't use.
- If using file provider with
watch: trueon a large directory, reduce the number of files. - Check for a large number of certificates in
acme.json. - Review
maxIdleConnsPerHostin ServersTransport — high values consume more memory.
T31. Slow startup
Symptom: Traefik takes a long time to become ready.
Fixes:
- Large numbers of Docker containers or Kubernetes resources slow initial discovery.
- Plugin loading can delay startup. Set
abortOnPluginFailure: trueto fail fast if a plugin cannot load. - Version checking runs 10 minutes after startup and does not affect startup time, but can generate early network traffic.
- For Kubernetes, check API server connectivity — a slow or unreachable API server delays provider initialization.
T32. Graceful shutdown timeout
Symptom: Traefik does not shut down cleanly on SIGTERM.
Traefik has a hard 10-second timeout for graceful shutdown. After 10 seconds,
if connections have not drained, the process terminates forcefully. The ping
endpoint returns the terminatingStatusCode (default 503) during shutdown to
signal upstream load balancers. Plan for long-lived connections (WebSockets,
SSE) accordingly.
7. FAQ
General
Q1. What is the difference between static and dynamic configuration?
Static configuration is loaded once at startup and cannot change without a restart. It defines entrypoints (ports), providers (where to find services), certificate resolvers, and global settings. Dynamic configuration is loaded continuously from providers and defines routers, services, middlewares, and TLS options. Dynamic configuration updates without restarting Traefik.
Q2. What Go version does Traefik v3 require?
Traefik v3 is built with Go 1.25.0. You only need Go installed if building from source. Docker images and binary releases include everything needed.
Q3. Does Traefik support HTTP/2?
Yes. Traefik supports HTTP/2 for both frontend (client-to-Traefik) and backend
(Traefik-to-service) connections. HTTP/2 is enabled by default for HTTPS
connections. To force HTTP/1.1 to backends, set disableHTTP2: true in the
ServersTransport.
Q4. Does Traefik support HTTP/3 (QUIC)?
HTTP/3 support is available as an experimental feature in Traefik v3.
Q5. Can I use both TOML and YAML for configuration?
You can use either for static configuration (pick one file). For dynamic configuration via the file provider, files can be TOML or YAML independently. You cannot mix formats within a single file.
Q6. How do I check which version I am running?
traefik version
Or check the dashboard header, which displays the version. The /api/version
endpoint also returns version information.
Q7. Does Traefik phone home?
By default, Traefik checks for new versions every 24 hours (first check 10 minutes after startup) and can send anonymous usage statistics. Disable both:
global:
checkNewVersion: false
sendAnonymousUsage: false
Q8. What happens if Traefik crashes mid-request?
Traefik handles SIGINT and SIGTERM for graceful shutdown with a 10-second drain period. If the process crashes (not a signal), in-flight connections are dropped. Use a process manager (systemd, Docker restart policies) to automatically restart Traefik.
Docker
Q9. Why do I need to mount the Docker socket?
Traefik needs to read container metadata (labels, ports, networks) from the
Docker daemon. The socket (/var/run/docker.sock) is the standard way to
communicate with Docker. Always mount it read-only (:ro) — Traefik only
reads, never writes.
Q10. What does exposedByDefault: false do?
When set to false, Traefik only considers containers that have the
traefik.enable=true label. When true (the default), every running container
is automatically exposed. Always set this to false in production.
Q11. How do I specify which port to use when a container exposes multiple ports?
Use the label:
traefik.http.services.myapp.loadbalancer.server.port=8080
Q12. How do I route to a container on a different Docker network?
You cannot. Traefik and the target container must share at least one Docker
network. Add the label traefik.docker.network=my-network to tell Traefik
which network to use when the container is on multiple networks.
Q13. Do I need Docker Swarm mode?
No. Traefik works with standalone Docker (using the Docker provider) and Docker Swarm (using the Swarm provider). Choose the provider that matches your setup.
Q14. How do I escape dollar signs in Docker Compose labels?
Double them: $$. Docker Compose interprets a single $ as a variable
reference. This is especially important for password hashes in BasicAuth
labels.
Certificates and TLS
Q15. Which ACME challenge should I use?
| Challenge | When to Use |
|---|---|
| HTTP | Port 80 is publicly accessible. Simplest setup. |
| TLS-ALPN | Port 80 is blocked, only port 443 is available. |
| DNS | Need wildcard certificates, or no public ports available. |
Q16. Can I use my own certificates instead of ACME?
Yes. Provide certificates via dynamic configuration:
tls:
certificates:
- certFile: "/certs/example.com.crt"
keyFile: "/certs/example.com.key"
Q17. How do I get wildcard certificates?
Only the DNS challenge supports wildcard certificates. Configure the DNS challenge with your DNS provider's credentials.
Q18. What TLS versions does Traefik support?
TLS 1.0 through TLS 1.3. The recommended minimum is TLS 1.2:
tls:
options:
default:
minVersion: "VersionTLS12"
Q19. What is X25519MLKEM768?
A post-quantum hybrid key exchange mechanism combining X25519 with ML-KEM-768. Traefik supports it as a curve preference for forward-looking protection against quantum computing threats.
Q20. Does Traefik support mTLS (mutual TLS)?
Yes. Configure client certificates in the ServersTransport for backend mTLS,
and use TLS options with clientAuth for frontend mTLS. Client certificate
information can be forwarded to backends via the PassTLSClientCert middleware.
Q21. Does Traefik handle OCSP stapling?
Yes, automatically. When certificates include OCSP responder URLs, Traefik fetches, caches, and staples OCSP responses during TLS handshakes. No configuration is needed.
Load Balancing
Q22. What is the default load balancing strategy?
Weighted Round Robin (wrr) is the default for ServersLoadBalancer services.
Q23. How do I do canary deployments?
Use a weighted service to split traffic between the current and canary versions:
http:
services:
canary:
weighted:
services:
- name: "v1"
weight: 90
- name: "v2"
weight: 10
Adjust weights as confidence grows.
Q24. What happens when a backend fails a health check?
The server is removed from the load balancer pool. Requests are routed only to healthy servers. When the server passes health checks again, it is re-added automatically.
Q25. Can I have active AND passive health checks?
Yes. Configure both on the same service. Active health checks probe periodically while passive health checks monitor real traffic for failures. They complement each other.
Middlewares
Q26. In what order are middlewares applied?
Middlewares are applied in the order they are listed in the router's
middlewares field. The first middleware in the list processes the request
first.
Q27. Can I share middlewares across providers?
Yes. Reference middlewares from other providers using the @provider suffix:
my-middleware@file, my-middleware@docker, my-middleware@kubernetes.
Q28. What is the default rate limit burst?
The default burst is 1. This means if average is 10 and period is 1s, the
client can make 10 requests per second with no bursting. Set burst higher to
allow short traffic spikes above the average.
Q29. How do I configure CORS?
Use the Headers middleware with the accessControl* fields:
http:
middlewares:
cors:
headers:
accessControlAllowOriginList:
- "https://app.example.com"
accessControlAllowMethods:
- "GET"
- "POST"
- "OPTIONS"
accessControlAllowHeaders:
- "Content-Type"
- "Authorization"
accessControlMaxAge: 86400
addVaryHeader: true
Q30. Is ipWhiteList still supported?
It still works but is deprecated. Migrate to ipAllowList, which has identical
functionality with a corrected name.
Q31. What status code does IPAllowList return for blocked requests?
The default rejectStatusCode is 403 Forbidden. You can customize it in the
middleware configuration.
Operations
Q32. How do I run Traefik as a systemd service?
Create a unit file with Type=notify (Traefik sends sd_notify readiness
signals) and set WatchdogSec for automatic restart on hang. See Section 5.7
for a complete example.
Q33. How do I monitor Traefik?
Enable one of the metrics backends (Prometheus, Datadog, StatsD, InfluxDB2,
or OTLP). Prometheus is the most common: set metrics.prometheus: {} and
scrape the /metrics endpoint on the Traefik entrypoint (default port 8080).
Q34. What should I back up?
Back up these files:
traefik.yml/traefik.toml(static configuration)- Dynamic configuration files (if using file provider)
acme.json(ACME certificates and private keys — critical)- Any custom TLS certificates and keys
Q35. Can I reload configuration without downtime?
Dynamic configuration (routers, services, middlewares) reloads automatically with zero downtime. Static configuration (entrypoints, providers) requires a restart. Architect your setup so that changes you make frequently are in dynamic configuration.
Q36. What signals does Traefik handle?
Traefik handles SIGINT and SIGTERM for graceful shutdown. It drains
connections for up to 10 seconds before forced termination.
Q37. How do I debug routing issues?
- Set
log.level: DEBUGfor verbose logging. - Check the dashboard for router/service status and health.
- Use
curl -vto inspect headers and status codes. - Verify that
Hostheaders match router rules. - Check network connectivity between Traefik and backends.
- Verify entrypoint port mapping in Docker Compose.
Q38. How do I handle log rotation?
When writing logs to a file, configure the built-in lumberjack-based rotation:
log:
filePath: "/var/log/traefik/traefik.log"
maxSize: 100 # MB before rotation
maxBackups: 5 # rotated files to keep
maxAge: 30 # days to retain
compress: true # gzip rotated files
Plugins
Q39. Where are plugins stored?
Remote plugins are downloaded to ./plugins-storage/ relative to Traefik's
working directory. Local plugins are loaded from ./plugins-local/.
Q40. What happens if a plugin fails to load?
By default, Traefik starts anyway and the plugin is simply unavailable. Set
experimental.abortOnPluginFailure: true to make Traefik refuse to start if
any plugin fails to load. This is recommended for production when plugins are
critical to your routing logic.
Architecture
Q41. What providers does Traefik support?
Docker, Docker Swarm, Kubernetes (CRD, Ingress, Gateway API, Knative), Consul Catalog, Nomad, ECS, File, HTTP, REST, KV stores (etcd, Redis, Consul KV, ZooKeeper), Tailscale, and plugin-based custom providers.
Q42. Can I use multiple providers simultaneously?
Yes. Enable multiple providers in static configuration. Traefik merges their
dynamic configurations. Use the @provider suffix to reference resources from
specific providers (e.g., my-service@docker, my-middleware@file).
Q43. What service types does Traefik support?
LoadBalancer (standard reverse proxy), WeightedRoundRobin (traffic splitting), HighestRandomWeight (consistent hashing), Mirroring (shadow traffic), and Failover (primary/backup).
Q44. How does Traefik handle graceful shutdown?
On receiving SIGINT or SIGTERM:
- Stops accepting new connections.
- Returns
terminatingStatusCode(default 503) on the/pingendpoint. - Drains existing connections for up to 10 seconds.
- Force-terminates if connections remain after 10 seconds.
Q45. Where can I find the API documentation?
The API is exposed on the Traefik entrypoint (default port 8080). The dashboard at that port provides a visual interface. See the Admin Guide for the full API endpoint reference.