LFCE 59 🐧 Reverse Proxy with Traefik
Traefik is a reverse proxy that treats configuration as dynamic data. Where Nginx reads its upstream blocks from a file at startup and Caddy reads its Caddyfile, Traefik watches external sources—Docker labels, Kubernetes resources, or a file directory—and updates its routing table at runtime without a restart . The previous chapters covered Nginx and Caddy, both of which are configured by writing directives in a file. Traefik’s model is different: the proxy is a watcher, and the routing rules are discovered from the infrastructure it serves.
The configuration is split into two layers. Install configuration (formerly static configuration) defines the entry points, the providers, and the API dashboard. Routing configuration (formerly dynamic configuration) defines the routers that match requests, the services that point to backends, and the middlewares that transform requests. The install configuration is loaded once at startup; the routing configuration is reloaded whenever the provider detects a change . This chapter covers both layers, with the file provider as the primary example and the Docker provider as the deployment pattern that makes Traefik distinctive.
Key point: A Traefik router matches requests by a rule and forwards them to a service. The rule is a boolean expression like Host(example.com) && PathPrefix(/api). The service defines the backend addresses and the load-balancing strategy. The middleware is an optional request modifier—authentication, rate limiting, prefix stripping—attached to the router. All three are part of the routing configuration, which the provider supplies at runtime.
Why Traefik’s configuration model is different
The discovery problem.
Nginx and Caddy require the administrator to know the backend addresses before writing the configuration. Traefik can discover them. When the Docker provider is enabled, Traefik watches the Docker socket for containers with traefik.enable=true labels. A container that is started with a routing label is added to the routing table; a container that is stopped is removed . This is the model that makes Traefik the default ingress controller for many container platforms: the deployment itself declares its routing, and the proxy discovers it.
The two-layer problem.
The install configuration and the routing configuration are separate because they change at different rates. The entry points (port 80, port 443) and the providers (Docker, file, Kubernetes) are set once and rarely changed. The routers, services, and middlewares change whenever a service is deployed or a rule is updated. Separating them means the proxy can reload the routing configuration without restarting the listeners or dropping active connections .
The middleware problem.
Traefik’s middlewares are a first-class concept, not a set of directives buried inside a location block. A middleware is declared once and attached to any router that needs it. The forwardAuth middleware delegates authentication to an external service: if the service returns 2xx, the request is allowed; otherwise the service’s response is returned . The stripPrefix middleware removes a path prefix before forwarding, which is the equivalent of Caddy’s handle_path. The redirectScheme middleware redirects HTTP to HTTPS. The same middleware can be reused across multiple routers .
a. Install configuration and the file provider
The install configuration defines the entry points and the providers. The file provider reads routing configuration from YAML or TOML files in a directory and watches for changes.
# /etc/traefik/traefik.yml (install configuration)
entryPoints:
web:
address: ":80"
websecure:
address: ":443"
providers:
file:
directory: "/etc/traefik/dynamic"
watch: true
The entryPoints define the listening ports. The providers.file block enables the file provider and points it at a directory. The watch: true option tells Traefik to reload the routing configuration when a file in the directory changes . The api.dashboard option enables the dashboard, which is served on the traefik entry point by default.
The routing configuration is a separate file in the dynamic directory. It defines the routers, services, and middlewares.
# /etc/traefik/dynamic/routes.yml (routing configuration)
http:
routers:
my-app:
rule: "Host(`app.example.com`)"
service: my-app-service
entryPoints:
- websecure
tls: {}
services:
my-app-service:
loadBalancer:
servers:
- url: "http://127.0.0.1:3000"
- url: "http://127.0.0.1:3001"
healthCheck:
path: /health
interval: 10s
timeout: 3s
The router my-app matches requests for app.example.com on the websecure entry point. The tls: {} section tells Traefik that the router is for HTTPS only . The service my-app-service defines two backend servers and a health check that probes /health every ten seconds. The health check removes an unhealthy server from the load-balancing rotation .
b. Routers, rules, and middlewares
A router’s rule is a boolean expression. The most common matchers are Host, Path, PathPrefix, and Headers. The && operator combines matchers, and the rule is evaluated against each incoming request.
http:
routers:
api-router:
rule: "Host(`example.com`) && PathPrefix(`/api`)"
service: api-service
middlewares:
- api-strip-prefix
- api-auth
web-router:
rule: "Host(`example.com`)"
service: web-service
middlewares:
api-strip-prefix:
stripPrefix:
prefixes:
- "/api"
api-auth:
forwardAuth:
address: "http://auth.internal:8080/verify"
authResponseHeaders:
- "X-User-Id"
The api-router matches requests for example.com that begin with /api. The middlewares list applies the api-strip-prefix middleware first, which removes the /api prefix before forwarding, and then the api-auth middleware, which delegates authentication to an external service . The authResponseHeaders option copies the X-User-Id header from the authentication response to the forwarded request, so the backend knows which user made the request.
The web-router matches every other request for example.com and forwards it to the web-service. The order of the routers matters when the rules overlap; the api-router has a more specific rule, and Traefik evaluates routers by priority. A router with a longer rule or an explicit priority value is evaluated first .
c. The Docker provider and automatic discovery
The Docker provider watches the Docker socket and creates routers from container labels. The container declares its own routing, and Traefik discovers it when the container starts.
# docker-compose.yml
services:
traefik:
image: traefik:v3.0
command:
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
ports:
- "80:80"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
app:
image: my-app:latest
labels:
- "traefik.enable=true"
- "traefik.http.routers.app.rule=Host(`app.example.com`)"
- "traefik.http.services.app.loadbalancer.server.port=3000"
The providers.docker.exposedbydefault=false setting means that containers are not exposed unless they have the traefik.enable=true label. The app container declares a router with the rule Host(app.example.com) and a service that points to port 3000 on the container . When the container starts, Traefik discovers the labels, creates the router and service, and begins forwarding traffic. When the container stops, the router is removed.
The Docker provider can also load-balance across multiple instances of the same service. If two containers have the same routing labels, Traefik creates a single service with two backend servers and distributes requests between them . The health check can be defined with labels as well, using the traefik.http.services.<name>.loadbalancer.healthcheck.* label prefix.
Complete Example Session
# ============================================
# PART 1: INSTALL TRAEFIK
# ============================================
# Using the official Docker image
docker pull traefik:v3.0
# ============================================
# PART 2: CREATE THE INSTALL CONFIGURATION
# ============================================
sudo mkdir -p /etc/traefik/dynamic
sudo tee /etc/traefik/traefik.yml > /dev/null <<'EOF'
entryPoints:
web:
address: ":80"
websecure:
address: ":443"
providers:
file:
directory: "/etc/traefik/dynamic"
watch: true
api:
dashboard: true
insecure: true
EOF
# ============================================
# PART 3: CREATE THE ROUTING CONFIGURATION
# ============================================
sudo tee /etc/traefik/dynamic/routes.yml > /dev/null <<'EOF'
http:
routers:
app:
rule: "Host(`app.localhost`)"
service: app-service
entryPoints:
- web
services:
app-service:
loadBalancer:
servers:
- url: "http://127.0.0.1:3000"
EOF
# ============================================
# PART 4: START A TEST BACKEND
# ============================================
python3 -m http.server 3000 --directory /var/www/app &
# ============================================
# PART 5: RUN TRAEFIK
# ============================================
docker run -d \
--name traefik \
-p 80:80 \
-p 8080:8080 \
-v /etc/traefik:/etc/traefik \
traefik:v3.0
# ============================================
# PART 6: ACCESS THE DASHBOARD
# ============================================
curl -s http://localhost:8080/api/rawdata | head -20
# Output shows the routers and services that Traefik has loaded
# ============================================
# PART 7: TEST THE ROUTER
# ============================================
curl -H "Host: app.localhost" http://localhost
# Output: the content from the backend
# ============================================
# PART 8: ADD A MIDDLEWARE
# ============================================
sudo tee /etc/traefik/dynamic/routes.yml > /dev/null <<'EOF'
http:
routers:
app:
rule: "Host(`app.localhost`)"
service: app-service
entryPoints:
- web
middlewares:
- add-header
middlewares:
add-header:
headers:
customRequestHeaders:
X-Proxy: "traefik"
services:
app-service:
loadBalancer:
servers:
- url: "http://127.0.0.1:3000"
EOF
# Traefik reloads automatically (watch: true)
sleep 2
curl -H "Host: app.localhost" http://localhost
# The backend receives the X-Proxy header
# ============================================
# PART 9: CHECK THE DASHBOARD
# ============================================
curl -s http://localhost:8080/api/http/routers | head -20
# Output shows the app router with its rule and middleware
# ============================================
# PART 10: CLEAN UP
# ============================================
kill %1
docker stop traefik && docker rm traefik
The ten parts demonstrate the Traefik setup: the install configuration, the routing configuration, a test backend, the container startup, the dashboard API, the router test, the middleware addition, and the automatic reload. The critical distinction is Part 8: the routing file is edited, and Traefik reloads it without a restart because the file provider has watch: true.
Quick Reference
Install vs Routing Configuration
| Layer | Content | Reload |
|---|---|---|
| Install (static) | Entry points, providers, API | Restart required |
| Routing (dynamic) | Routers, services, middlewares | Runtime reload |
Core Routing Objects
| Object | Purpose | Example |
|---|---|---|
| Router | Matches requests and forwards to a service | rule: "Host(app.com)" |
| Service | Defines backend servers and load balancing | loadBalancer.servers |
| Middleware | Modifies requests or responses | stripPrefix, forwardAuth |
Common Matchers
| Matcher | Purpose |
|---|---|
Host | Match the hostname |
Path | Match the exact path |
PathPrefix | Match the path prefix |
Headers | Match a request header |
&& / || | Combine matchers |
Load-Balancing Strategies
| Strategy | Behavior |
|---|---|
wrr (default) | Weighted round-robin |
p2c | Pick least-loaded of two random servers |
hrw | Consistent hashing by client IP |
Health Check Options
| Option | Purpose |
|---|---|
path | Health check endpoint |
interval | Frequency for healthy servers |
timeout | Maximum wait for response |
status | Expected status code |
Best Practices
✅ Do This:
# Separate install and routing configuration
# Install config defines entry points and providers # ✅
# Routing config defines routers, services, middlewares # ✅
# Use the Docker provider with exposedByDefault false
providers.docker.exposedbydefault=false # ✅
# Attach health checks to services
healthCheck:
path: /health
interval: 10s # ✅
# Use stripPrefix for backends that do not expect the prefix
middlewares:
- api-strip-prefix # ✅
# Use forwardAuth for authentication
forwardAuth:
address: "http://auth:8080/verify" # ✅
# Watch the routing directory for changes
providers.file.watch=true # ✅
❌ Don’t Do This:
# Expose every container by default
providers.docker.exposedbydefault=true # Security risk # ❌
# Define routing in the install configuration
# It is not reloaded at runtime # ❌
# Omit health checks for multi-server services
loadBalancer.servers:
- url: "http://app1:3000" # ❌
# Use the wrong middleware order
# Middlewares apply in the order they are listed # ❌
# Forget the trailing slash on the dashboard path
# The dashboard API requires the exact path # ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Router not matching | Rule syntax error or host mismatch | Check the rule with api/rawdata |
| Service unreachable | Backend URL wrong or server down | curl the backend directly |
| Routing changes not applied | Provider not watching | Set watch: true for the file provider |
| Docker container not discovered | traefik.enable=true missing | Add the label |
| 404 from Traefik | No router matched the request | Check the rule and the entry point |
| Middleware not applied | Not listed in the router’s middlewares | Add it to the list |
Real-World Examples
1. Basic file provider router
http:
routers:
app:
rule: "Host(`app.example.com`)"
service: app-service
2. Service with two backends
http:
services:
app-service:
loadBalancer:
servers:
- url: "http://10.0.0.1:3000"
- url: "http://10.0.0.2:3000"
3. Health check
healthCheck:
path: /health
interval: 10s
4. Strip prefix middleware
api-strip-prefix:
stripPrefix:
prefixes:
- "/api"
5. Forward auth middleware
api-auth:
forwardAuth:
address: "http://auth:8080/verify"
6. Redirect HTTP to HTTPS
redirect-https:
redirectScheme:
scheme: https
7. Docker provider
providers:
docker:
exposedByDefault: false
8. Docker labels
labels:
- "traefik.enable=true"
- "traefik.http.routers.app.rule=Host(`app.example.com`)"
9. Dashboard API
curl http://localhost:8080/api/http/routers
10. Test a router
curl -H "Host: app.example.com" http://localhost
Visual
Traefik Architecture
┌──────────────────────────────────────────────────────────────┐
│ TRAEFIK WATCHES PROVIDERS AND UPDATES ROUTING AT RUNTIME │
│ │
│ Providers: │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Docker │ │ File │ │ K8s │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │
│ └────────────┼────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Traefik │ │
│ │ │ │
│ │ Router: rule → service + middlewares │ │
│ │ Service: load balancer + health checks │ │
│ │ Middleware: auth, strip, redirect, headers │ │
│ └──────────────────────────┬─────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Backends │ │
│ └────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
Router Rule Evaluation
┌──────────────────────────────────────────────────────────────┐
│ A ROUTER RULE IS A BOOLEAN EXPRESSION │
│ │
│ rule: "Host(`example.com`) && PathPrefix(`/api`)" │
│ │
│ Request: GET https://example.com/api/users │
│ │ │
│ ├── Host(`example.com`) → true │
│ ├── PathPrefix(`/api`) → true │
│ └── true && true → MATCH │
│ │
│ Request: GET https://example.com/about │
│ │ │
│ ├── Host(`example.com`) → true │
│ ├── PathPrefix(`/api`) → false │
│ └── true && false → NO MATCH │
└──────────────────────────────────────────────────────────────┘
Docker Provider Discovery
┌──────────────────────────────────────────────────────────────┐
│ CONTAINERS DECLARE THEIR OWN ROUTING │
│ │
│ docker-compose.yml: │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ app: │ │
│ │ labels: │ │
│ │ - "traefik.enable=true" │ │
│ │ - "traefik.http.routers.app.rule=Host(`app.com`)" │ │
│ └────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ Traefik watches the Docker socket │
│ │ │
│ ├── container starts → router created │
│ └── container stops → router removed │
└──────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Install configuration | Entry points, providers, API |
| Routing configuration | Routers, services, middlewares |
| Router rule | Boolean expression (Host, PathPrefix, etc.) |
| Service | Backend addresses and load balancing |
| Middleware | Request/response modifiers |
| File provider | YAML/TOML files in a watched directory |
| Docker provider | Container labels with traefik.enable=true |
| Default load-balancing | Weighted round-robin |
| Health checks | healthCheck on the service |
| Dashboard API | /api/http/routers, /api/rawdata |
Key takeaways:
- Traefik separates install configuration from routing configuration. The install configuration defines the entry points and providers and requires a restart to change. The routing configuration defines routers, services, and middlewares and reloads at runtime when the provider detects a change .
- A router matches requests by a boolean rule and forwards to a service. The rule can combine
Host,PathPrefix,Headers, and other matchers with&&and||. The service defines the backend addresses and the load-balancing strategy . - Middlewares are reusable request modifiers. The
stripPrefixmiddleware removes a path prefix, theforwardAuthmiddleware delegates authentication to an external service, and theredirectSchememiddleware redirects HTTP to HTTPS . - The Docker provider discovers routing from container labels. A container with
traefik.enable=trueand a router rule label is added to the routing table when it starts and removed when it stops . - Health checks remove unhealthy backends from rotation. The
healthCheckblock on the service defines the endpoint, the interval, and the timeout. A server that fails the check is not selected for new requests . - The dashboard API shows the active routing configuration. The
/api/http/routersand/api/rawdataendpoints list the routers, services, and middlewares that Traefik has loaded, which is the definitive verification that the configuration is active .
Remember: Traefik is a reverse proxy that watches its environment. The install configuration sets up the listeners and the providers. The routing configuration defines the routers, services, and middlewares that handle requests. The provider—file, Docker, or Kubernetes—supplies the routing configuration at runtime, and Traefik updates its table without a restart. A router matches by rule, a service points to backends, and a middleware modifies the request. The Docker provider makes the deployment itself declare its routing, which is why Traefik is the default ingress for many container platforms.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!