Loading...
Loading...
Traefik v3 cloud-native reverse proxy. Covers providers, entrypoints, routers, middlewares, services, Docker labels, TLS/ACME, dashboard, and metrics. USE WHEN: user mentions "traefik", "traefik v3", "traefik docker", "traefik labels", "traefik middleware", "traefik dashboard", "traefik tls", "traefik acme", "traefik router", "traefik entrypoint", "traefik reverse proxy", "traefik cloudflare", "traefik let's encrypt", "traefik rate limit" DO NOT USE FOR: Caddy-based setups - use `caddy` skill, Nginx load balancing - use `load-balancer` skill, Kubernetes ingress with nginx-ingress - use `kubernetes` skill, Application-level TLS inside app code
npx skill4agent add claude-dev-suite/claude-dev-suite traefik┌─────────────────────────────────────────────────────────────────┐
│ PROVIDERS ENTRYPOINTS ROUTERS SERVICES │
│ ────────── ─────────── ─────── ──────── │
│ Docker labels → :80 (web) → Host rule → LB pool │
│ File provider → :443 (websecure) PathPrefix backend │
│ Kubernetes → :8080 (dashboard) Headers servers │
└─────────────────────────────────────────────────────────────────┘traefik.yml# /etc/traefik/traefik.yml (or mounted at /traefik.yml in Docker)
# Global settings
global:
checkNewVersion: false
sendAnonymousUsage: false
# API & dashboard
api:
dashboard: true
insecure: false # NEVER true in production
# Entrypoints
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
permanent: true
websecure:
address: ":443"
http:
tls:
certResolver: letsencrypt
middlewares:
- security-headers@file # Apply to all HTTPS routes
metrics:
address: ":8082"
# Certificate resolvers
certificatesResolvers:
letsencrypt:
acme:
email: devops@company.com
storage: /letsencrypt/acme.json # Persistent volume required
# HTTP challenge (default) — requires port 80 open
httpChallenge:
entryPoint: web
letsencrypt-dns:
acme:
email: devops@company.com
storage: /letsencrypt/acme-dns.json
dnsChallenge:
provider: cloudflare # Set CF_DNS_API_TOKEN env var
delayBeforeCheck: 30 # Wait for DNS propagation
# Providers
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false # IMPORTANT: require explicit opt-in
network: traefik-public # Default network for container comms
file:
directory: /etc/traefik/dynamic/ # Watch for changes automatically
watch: true
# Logging
log:
level: INFO # DEBUG | INFO | WARN | ERROR
filePath: /var/log/traefik/traefik.log
# Access logs
accessLog:
filePath: /var/log/traefik/access.log
bufferingSize: 100
fields:
headers:
defaultMode: drop
names:
User-Agent: keep
X-Forwarded-For: keep
# Metrics
metrics:
prometheus:
entryPoint: metrics
addServicesLabels: true
addRoutersLabels: true# docker-compose.yml
version: "3.9"
services:
traefik:
image: traefik:v3.3
container_name: traefik
restart: unless-stopped
security_opt:
- no-new-privileges:true
ports:
- "80:80"
- "443:443"
volumes:
- /etc/traefik/traefik.yml:/traefik.yml:ro
- /etc/traefik/dynamic/:/etc/traefik/dynamic/:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
- traefik-letsencrypt:/letsencrypt
environment:
- CF_DNS_API_TOKEN=${CF_DNS_API_TOKEN}
networks:
- traefik-public
labels:
- "traefik.enable=true"
# Dashboard router
- "traefik.http.routers.dashboard.rule=Host(`traefik.example.com`)"
- "traefik.http.routers.dashboard.entrypoints=websecure"
- "traefik.http.routers.dashboard.tls.certresolver=letsencrypt"
- "traefik.http.routers.dashboard.service=api@internal"
- "traefik.http.routers.dashboard.middlewares=dashboard-auth"
# Dashboard basic auth: echo $(htpasswd -nbB admin 'password') | sed -e s/\\$/\\$\\$/g
- "traefik.http.middlewares.dashboard-auth.basicauth.users=admin:$$2y$$10$$hash..."
# Example application
api:
image: myapp/api:1.4.2
restart: unless-stopped
networks:
- traefik-public
- internal
environment:
- DATABASE_URL=postgres://user:pass@db:5432/app
labels:
- "traefik.enable=true"
- "traefik.http.routers.api.rule=Host(`api.example.com`)"
- "traefik.http.routers.api.entrypoints=websecure"
- "traefik.http.routers.api.tls.certresolver=letsencrypt"
- "traefik.http.routers.api.middlewares=rate-limit,security-headers"
# Service port (required when container exposes multiple ports)
- "traefik.http.services.api.loadbalancer.server.port=3000"
# Health check
- "traefik.http.services.api.loadbalancer.healthcheck.path=/health"
- "traefik.http.services.api.loadbalancer.healthcheck.interval=10s"
- "traefik.http.services.api.loadbalancer.healthcheck.timeout=3s"
# Frontend app with path-based routing
frontend:
image: myapp/frontend:2.1.0
restart: unless-stopped
networks:
- traefik-public
labels:
- "traefik.enable=true"
- "traefik.http.routers.frontend.rule=Host(`example.com`) || Host(`www.example.com`)"
- "traefik.http.routers.frontend.entrypoints=websecure"
- "traefik.http.routers.frontend.tls.certresolver=letsencrypt"
- "traefik.http.routers.frontend.middlewares=www-redirect,security-headers"
- "traefik.http.services.frontend.loadbalancer.server.port=80"
db:
image: postgres:16-alpine
restart: unless-stopped
networks:
- internal # Not on traefik-public — no external routing
volumes:
- postgres-data:/var/lib/postgresql/data
environment:
- POSTGRES_PASSWORD=${DB_PASSWORD}
networks:
traefik-public:
external: true # Pre-created: docker network create traefik-public
internal:
driver: bridge
volumes:
traefik-letsencrypt:
postgres-data:# /etc/traefik/dynamic/middlewares.yml
http:
middlewares:
# HTTP → HTTPS redirect (also configured at entrypoint level above)
redirect-to-https:
redirectScheme:
scheme: https
permanent: true
# HSTS + security headers
security-headers:
headers:
stsSeconds: 31536000
stsIncludeSubdomains: true
stsPreload: true
forceSTSHeader: true
contentTypeNosniff: true
browserXssFilter: true
referrerPolicy: "strict-origin-when-cross-origin"
frameDeny: true
customResponseHeaders:
X-Powered-By: ""
Server: ""
# Rate limiting
rate-limit:
rateLimit:
average: 100 # Requests per second (average)
burst: 50 # Burst allowance
period: 1m # Window period
# Strip /api prefix before forwarding
strip-api-prefix:
stripPrefix:
prefixes:
- "/api"
# Add /v1 prefix
add-v1-prefix:
addPrefix:
prefix: "/v1"
# Basic auth
internal-auth:
basicAuth:
usersFile: /etc/traefik/users.htpasswd
removeHeader: true # Strip Authorization before passing to upstream
# IP whitelist (Traefik v3: use ipAllowList)
office-only:
ipAllowList:
sourceRange:
- "10.0.0.0/8"
- "203.0.113.42/32"
# Retry on failure
retry-middleware:
retry:
attempts: 3
initialInterval: 100ms
# www redirect
www-redirect:
redirectRegex:
regex: "^https?://www\\.example\\.com/(.*)"
replacement: "https://example.com/${1}"
permanent: true
# Circuit breaker
circuit-breaker:
circuitBreaker:
expression: "ResponseCodeRatio(500, 600, 0, 600) > 0.25 || NetworkErrorRatio() > 0.10"# /etc/traefik/dynamic/services.yml — external services (not in Docker)
http:
services:
legacy-monolith:
loadBalancer:
servers:
- url: "http://192.168.1.10:8080"
- url: "http://192.168.1.11:8080"
healthCheck:
path: /status
interval: 15s
timeout: 5s
sticky:
cookie:
name: SERVERID
secure: true
httpOnly: true
routers:
legacy:
rule: "Host(`legacy.example.com`)"
entryPoints:
- websecure
tls:
certResolver: letsencrypt
service: legacy-monolith
middlewares:
- security-headers
- rate-limitRequest → rate-limit → ip-allowlist → strip-api-prefix → upstream
Response ← security-headers (applied on response) ←──────────────# Labels on a service container
- "traefik.http.routers.myapp.middlewares=rate-limit@file,office-only@file,strip-api-prefix@file,security-headers@file"@file@docker# /etc/traefik/dynamic/tcp.yml
tcp:
routers:
postgres:
rule: "HostSNI(`*`)" # TCP passthrough (no TLS inspection)
entryPoints:
- postgres # entryPoint address: ":5432"
service: postgres-backend
services:
postgres-backend:
loadBalancer:
servers:
- address: "10.0.1.5:5432"| Anti-Pattern | Problem | Solution |
|---|---|---|
| Dashboard exposed on port 8080 with no auth | Set |
| Every container automatically gets a route, including databases | Always set |
| Wildcard cert with HTTP challenge | HTTP challenge cannot prove DNS control for wildcards | Use |
| Not pinning Traefik version | | Pin to exact version: |
Missing | Traefik picks wrong port arbitrarily | Always specify the service port label explicitly |
Storing | Certificates lost on container restart → rate limit hit | Mount |
| Applying heavy middlewares (auth, rate-limit) only on some routes | Inconsistent security posture | Apply security-headers globally via entrypoint middleware; add auth/rate-limit to sensitive routers |
| Router rules without priority on overlapping paths | Non-deterministic routing when multiple rules match | Add |
| Docker socket mounted as read-write with no protection | Container escape via Docker API | Use socket proxy (e.g., |
| No health checks on services | Traefik routes to unhealthy containers | Configure |
| Symptom | Likely Cause | Fix |
|---|---|---|
| Router not matching — 404 from Traefik | Label typo, wrong entrypoint name, container not on Traefik network | Check |
| Certificate not issued | Port 80 blocked (HTTP challenge) or DNS credentials wrong (DNS challenge) | |
| Middleware not applied | | Use exact |
| "404 page not found" from upstream app | App routing issue, not Traefik | Bypass Traefik: |
| Dashboard shows router but requests still fail | Middleware blocking (auth, IP whitelist) | Check access log; temporarily remove middlewares to isolate |
| Let's Encrypt client refuses to read file | |
| TLS certificate is self-signed (Traefik default) | ACME not configured, or cert resolver name wrong on router | Ensure |
| Container restarts but keeps same cert | acme.json not updated | Ensure acme.json volume is persistent; delete acme.json and restart to force re-issue (if not rate-limited) |
| High memory usage | Too many access log entries buffered | Increase |
| Sticky sessions not working | Cookie not forwarded by load balancer | Ensure |
latestexposedByDefault: falseapi.insecure: falseacme.json600/var/run/docker.sock@provider