Traefik V3 Configuration guide
On this page 13 sections
- Traefik Reverse Proxy Configuration Guide
- Overview
- Core Traefik Setup
- Network Architecture
- Routing Docker Containers
- Multi-Network Container Isolation
- External Service Routing
- Authentik Integration
- CrowdSec Integration
- Application-Specific Configurations
- Troubleshooting
- Security Best Practices
- Appendix: Complete Working Examples
Traefik Reverse Proxy Configuration Guide
Overview
This guide documents the configuration of Traefik as a centralized reverse proxy for internal services. All traffic is terminated with Cloudflare Origin Certificates, providing end-to-end encryption without exposing services directly to the internet.
Architecture Decisions
- Single Traefik Instance: All services route through one proxy container
- Network Segmentation: Backend databases isolated from the proxy network
- File + Docker Providers: Docker labels for containers, YAML files for external hosts
- Authentik ForwardAuth: Centralized SSO for applications lacking native authentication
- CrowdSec Protection: Real-time IP reputation and behavior-based blocking
Core Traefik Setup
Directory Structure
/home/ed/docker/traefik/
├── docker-compose.yml
├── certs/
│ ├── origin.pem
│ └── privatekey.pem
├── dynamic/
│ ├── tls.yaml
│ ├── headers.yaml
│ ├── crowdsec.yaml
│ ├── plex.yaml
│ ├── qbittorrent.yaml
│ └── home-assistant.yaml
└── logs/
└── access.log
Traefik Docker Compose
version: "3.9"
services:
traefik:
image: traefik:v3.4
container_name: traefik
restart: unless-stopped
security_opt:
- no-new-privileges:true
networks:
- traefik
ports:
- "80:80"
- "443:443"
- "8080:8080" # Dashboard (optional: remove in production)
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./certs:/certs:ro
- ./dynamic:/dynamic:ro
- ./logs:/logs
command:
# EntryPoints
- "--entrypoints.web.address=:80"
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
- "--entrypoints.web.http.redirections.entrypoint.permanent=true"
- "--entrypoints.websecure.address=:443"
- "--entrypoints.websecure.http.tls=true"
# File Provider (watches directory for external service configs)
- "--providers.file.directory=/dynamic/"
- "--providers.file.watch=true"
# Docker Provider
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--providers.docker.network=traefik"
# API & Dashboard
- "--api.dashboard=true"
- "--api.insecure=false"
# Observability
- "--log.level=INFO"
- "--accesslog=true"
- "--accesslog.format=json"
- "--accesslog.filepath=/logs/access.log"
labels:
- "traefik.enable=true"
# Dashboard Router
- "traefik.http.routers.dashboard.rule=Host(`traefik.dashboard.fullmetaled.com`)"
- "traefik.http.routers.dashboard.entrypoints=websecure"
- "traefik.http.routers.dashboard.service=api@internal"
- "traefik.http.routers.dashboard.tls=true"
# Basic Auth Middleware
- "traefik.http.middlewares.dashboard-auth.basicauth.users=admin:$$apr1$$lgiS599s$$dsaecQPIetGM8SQTVd.pN/"
- "traefik.http.routers.dashboard.middlewares=dashboard-auth@docker"
networks:
traefik:
name: traefik
TLS Configuration (dynamic/tls.yaml)
tls:
certificates:
- certFile: /certs/origin.pem
keyFile: /certs/privatekey.pem
Network Architecture
The traefik Network
All services that need to be accessible via the proxy must be on the traefik network. This network is created by the Traefik stack and referenced externally by other compose files.
Network Isolation Pattern
For services with databases, use a dual-network approach:
- Frontend Container: Attached to both
traefik(for ingress) and an internal network (for backend communication) - Database Container: Attached only to the internal network
This prevents the proxy from having direct access to databases, improving security posture.
Routing Docker Containers
Simple Single-Network Service
Example: Whoami Test Container
version: "3.9"
services:
whoami:
image: traefik/whoami
container_name: whoami
networks:
- traefik
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami.rule=Host(`whoami.fullmetaled.com`)"
- "traefik.http.routers.whoami.entrypoints=websecure"
- "traefik.http.routers.whoami.tls=true"
- "traefik.http.services.whoami.loadbalancer.server.port=80"
networks:
traefik:
external: true
Key Label Breakdown
| Label | Purpose |
|---|---|
traefik.enable=true |
Opt-in to Traefik routing (required when exposedbydefault=false) |
traefik.http.routers.whoami.rule |
Host-based routing rule |
traefik.http.routers.whoami.entrypoints |
Which entrypoint to listen on (use websecure for HTTPS) |
traefik.http.routers.whoami.tls=true |
Enable TLS termination |
traefik.http.services.whoami.loadbalancer.server.port |
Explicit backend port (prevents ambiguity) |
Multi-Network Container Isolation
Example: BookStack with Isolated Database
version: "3.9"
services:
bookstack:
image: lscr.io/linuxserver/bookstack
container_name: bookstack
environment:
- APP_URL=https://docs.fullmetaled.com
- DB_HOST=bookstack_db
- DB_USER=bookstack
- DB_PASS=<secure_password>
- DB_DATABASE=bookstackapp
volumes:
- ./bookstack_app_data:/config
restart: always
depends_on:
- bookstack_db
networks:
- traefik # Proxy access
- bookstack-internal # Database access
labels:
- "traefik.enable=true"
- "traefik.http.routers.bookstack.rule=Host(`docs.fullmetaled.com`)"
- "traefik.http.routers.bookstack.entrypoints=websecure"
- "traefik.http.routers.bookstack.tls=true"
- "traefik.http.services.bookstack.loadbalancer.server.port=80"
# CRITICAL: Tells Traefik which network to use for routing
- "traefik.docker.network=traefik"
bookstack_db:
image: lscr.io/linuxserver/mariadb
container_name: bookstack_db
environment:
- MYSQL_DATABASE=bookstackapp
- MYSQL_USER=bookstack
- MYSQL_PASSWORD=<secure_password>
- MYSQL_ROOT_PASSWORD=<root_password>
volumes:
- ./bookstack_db_data:/config
networks:
- bookstack-internal # NOT on traefik network
restart: always
networks:
traefik:
external: true
bookstack-internal:
driver: bridge
Critical Configuration Points
traefik.docker.network=traefik: When a container is on multiple networks, this label explicitly tells Traefik which interface to route to- Database Isolation: The database container has no connection to the
traefiknetwork, preventing lateral movement in case of proxy compromise - Service Resolution: Docker's internal DNS allows
bookstackto resolvebookstack_dbvia the shared internal network
External Service Routing
For services running on different physical/virtual machines (non-Docker), use the File Provider to manually define routes.
Plex Media Server (Unprotected)
File: dynamic/plex.yaml
http:
routers:
plex-router:
rule: "Host(`plex.fullmetaled.com`)"
entryPoints:
- websecure
service: plex-service
tls: {}
services:
plex-service:
loadBalancer:
servers:
- url: "http://192.168.1.100:32400"
passHostHeader: true
Plex Configuration:
- Navigate to Settings > Server > Network
- Add to Custom server access URLs:
https://plex.fullmetaled.com:443
qBittorrent (Protected with Authentik)
File: dynamic/qbittorrent.yaml
http:
routers:
qbittorrent-router:
rule: "Host(`qbit.fullmetaled.com`)"
entryPoints:
- websecure
middlewares:
- authentik@file # Reference to Authentik ForwardAuth
service: qbittorrent-service
tls: {}
services:
qbittorrent-service:
loadBalancer:
servers:
- url: "http://192.168.1.101:8080"
passHostHeader: true
qBittorrent Configuration:
- Tools > Options > Web UI
- Disable "Enable Cross-site Request Forgery (CSRF) protection"
- Enable "Trusted proxies list" → Add Traefik host IP
Home Assistant
File: dynamic/home-assistant.yaml
http:
routers:
home-assistant-router:
rule: "Host(`ha.fullmetaled.com`)"
entryPoints:
- websecure
service: home-assistant-service
tls: {}
services:
home-assistant-service:
loadBalancer:
servers:
- url: "http://192.168.1.102:8123"
passHostHeader: true
Home Assistant Configuration (configuration.yaml):
http:
use_x_forwarded_for: true
trusted_proxies:
- 192.168.1.50 # IP of Traefik host
Critical: Restart Home Assistant after modifying configuration.yaml (a simple reload is insufficient).
Authentik Integration
Middleware Configuration
File: dynamic/headers.yaml
http:
middlewares:
authentik:
forwardAuth:
address: http://authentik:9000/outpost.goauthentik.io/auth/traefik
trustForwardHeader: true
authResponseHeaders:
- X-authentik-username
- X-authentik-groups
- X-authentik-email
- X-authentik-name
- X-authentik-uid
- X-authentik-jwt
- X-authentik-meta-jwks
- X-authentik-meta-outpost
- X-authentik-meta-provider
- X-authentik-meta-app
- X-authentik-meta-version
Authentik Container Requirements
The Authentik container must:
- Be named
authentik(or update theaddressURL accordingly) - Be on the
traefiknetwork - Expose port 9000
Using Authentik with Docker Containers
Add to container labels:
- "traefik.http.routers.app-name.middlewares=authentik@file"
Using Authentik with External Services
Add to the router definition in the YAML file:
middlewares:
- authentik@file
Authentik Provider Configuration
For each protected application:
- Applications > Providers > Create → Select "Proxy Provider"
- External Host: Must match the Traefik
Host()rule exactly (e.g.,https://qbit.fullmetaled.com) - Forward auth (single application): Select this mode
- Create Application and link to this provider
- Outposts > Default Embedded Outpost → Ensure it is assigned to the provider
CrowdSec Integration
CrowdSec provides collaborative intrusion prevention by analyzing traffic patterns and blocking malicious IPs based on real-time threat intelligence. Integration with Traefik occurs via a ForwardAuth bouncer middleware.
Architecture Overview
Client Request → Traefik → CrowdSec Bouncer (checks IP) → Backend Service
↓
CrowdSec LAPI (Local API)
↓
Decision Database
CrowdSec Stack Deployment
Directory Structure:
/home/ed/docker/crowdsec/
├── docker-compose.yml
├── config/
│ └── acquis.yaml
└── data/
Docker Compose (crowdsec/docker-compose.yml):
version: "3.9"
services:
crowdsec:
image: crowdsecurity/crowdsec:latest
container_name: crowdsec
restart: always
environment:
# Collections define what patterns to detect
COLLECTIONS: "crowdsecurity/traefik crowdsecurity/http-cve"
# Generate this key: openssl rand -base64 32
BOUNCER_KEY_traefik: "<generated_bouncer_key>"
volumes:
- ./config/acquis.yaml:/etc/crowdsec/acquis.yaml:ro
- ./data:/var/lib/crowdsec/data
# Mount Traefik logs for analysis
- /home/ed/docker/traefik/logs:/var/log/traefik:ro
networks:
- traefik
bouncer:
image: fbonalair/traefik-crowdsec-bouncer:latest
container_name: crowdsec-bouncer
restart: always
environment:
CROWDSEC_BOUNCER_API_KEY: "<generated_bouncer_key>"
CROWDSEC_AGENT_HOST: crowdsec:8080
# Return HTTP 403 instead of a ban page
CROWDSEC_BOUNCER_BAN_RESPONSE_CODE: 403
networks:
- traefik
depends_on:
- crowdsec
networks:
traefik:
external: true
CrowdSec Acquisition Configuration
File: crowdsec/config/acquis.yaml
filenames:
- /var/log/traefik/access.log
labels:
type: traefik
This tells CrowdSec to parse Traefik's access logs and apply the Traefik-specific detection patterns.
Bouncer Middleware Configuration
File: traefik/dynamic/crowdsec.yaml
http:
middlewares:
crowdsec-bouncer:
forwardAuth:
address: http://crowdsec-bouncer:8080/api/v1/forwardAuth
trustForwardHeader: true
Applying CrowdSec Protection
Option 1: Protect Specific Docker Containers
Add to container labels:
labels:
- "traefik.enable=true"
- "traefik.http.routers.app.rule=Host(`app.fullmetaled.com`)"
- "traefik.http.routers.app.entrypoints=websecure"
- "traefik.http.routers.app.tls=true"
# Apply CrowdSec before other middlewares
- "traefik.http.routers.app.middlewares=crowdsec-bouncer@file,authentik@file"
Option 2: Protect External Services
File: dynamic/protected-service.yaml
http:
routers:
protected-router:
rule: "Host(`service.fullmetaled.com`)"
entryPoints:
- websecure
middlewares:
- crowdsec-bouncer@file
service: protected-service
tls: {}
services:
protected-service:
loadBalancer:
servers:
- url: "http://192.168.1.103:80"
passHostHeader: true
Option 3: Global Protection (All Services)
Apply at the entrypoint level in Traefik static config:
command:
- "--entrypoints.websecure.http.middlewares=crowdsec-bouncer@file"
Warning: This applies CrowdSec to all routes, including the Traefik dashboard. Ensure you whitelist your management IPs (see Whitelist Configuration below).
CrowdSec Management Commands
# Access CrowdSec CLI
docker exec crowdsec cscli
# View current bans
docker exec crowdsec cscli decisions list
# Manually ban an IP
docker exec crowdsec cscli decisions add --ip 192.168.1.50 --duration 4h --reason "Manual ban"
# Unban an IP
docker exec crowdsec cscli decisions delete --ip 192.168.1.50
# View metrics
docker exec crowdsec cscli metrics
# View active scenarios (detection patterns)
docker exec crowdsec cscli scenarios list
# Install additional collections
docker exec crowdsec cscli collections install crowdsecurity/nginx
Whitelist Configuration
To prevent CrowdSec from blocking trusted IPs (e.g., your management network):
File: crowdsec/config/parsers/s02-enrich/mywhitelists.yaml
name: crowdsecurity/whitelists
description: "Whitelist internal management IPs"
whitelist:
reason: "Internal network"
ip:
- "192.168.1.0/24"
- "10.0.0.0/8"
expression:
# Don't ban your own monitoring tools
- evt.Parsed.http_user_agent contains 'Uptime-Kuma'
After creating the whitelist, reload CrowdSec:
docker exec crowdsec cscli parsers install crowdsecurity/whitelists
docker restart crowdsec
CrowdSec Dashboard (Optional)
For visual monitoring via Metabase:
dashboard:
image: metabase/metabase:latest
container_name: crowdsec-dashboard
restart: always
ports:
- "3000:3000"
environment:
MB_DB_FILE: /data/metabase.db
MGID: "1"
volumes:
- ./dashboard-data:/data
networks:
- traefik
depends_on:
- crowdsec
After starting, access http://<host>:3000 and configure using the CrowdSec database at /var/lib/crowdsec/data/crowdsec.db.
Testing CrowdSec Integration
1. Verify Bouncer Communication
# Check bouncer logs
docker logs crowdsec-bouncer
# Should see: "Successfully connected to CrowdSec LAPI"
2. Trigger a Test Ban
# Manually ban your client IP
docker exec crowdsec cscli decisions add --ip <your_ip> --duration 5m --reason "Test"
# Try accessing a protected service - should receive 403
curl -I https://app.fullmetaled.com
# Remove the ban
docker exec crowdsec cscli decisions delete --ip <your_ip>
3. Simulate Attack Pattern
Use nikto or gobuster to trigger HTTP vulnerability scanning detection:
# This will trigger the http-probing scenario
nikto -h https://app.fullmetaled.com
# Check if your IP was banned
docker exec crowdsec cscli decisions list
Middleware Chaining Best Practices
When combining CrowdSec with other middlewares:
# Recommended order:
middlewares:
- crowdsec-bouncer@file # Block malicious IPs first
- authentik@file # Then authenticate
- rate-limit@file # Then rate limit
This ensures banned IPs are rejected before consuming authentication resources.
CrowdSec Performance Considerations
- Log Volume: CrowdSec parses logs in real-time. High-traffic environments may require tuning the
acquisition.yamlto usepoll_without_inotify: true - Database Growth: The decision database grows over time. Set up log rotation and periodic cleanup:
# Remove decisions older than 30 days docker exec crowdsec cscli decisions delete --range 192.168.0.0/16 --type ban --older-than 30d - Bouncer Latency: Each request adds ~5-10ms for the ForwardAuth check. For latency-sensitive apps, consider applying CrowdSec selectively
Common CrowdSec Scenarios
| Scenario | Description | Trigger |
|---|---|---|
crowdsecurity/http-probing |
Port/vulnerability scanning | Nikto, Nuclei, etc. |
crowdsecurity/http-crawl-non_statics |
Aggressive crawling | Rapid sequential requests |
crowdsecurity/http-bad-user-agent |
Malicious user agents | Known bot signatures |
crowdsecurity/http-path-traversal-probing |
Directory traversal attempts | ../ patterns in URLs |
crowdsecurity/http-sqli-probing |
SQL injection attempts | SQL keywords in parameters |
Troubleshooting CrowdSec
Issue: Bouncer returns 500 errors
Solution: Check API key matches between CrowdSec and bouncer:
docker exec crowdsec cscli bouncers list
# Verify the bouncer is registered and active
Issue: Legitimate users getting banned
Solution: Review decisions and add whitelist:
docker exec crowdsec cscli decisions list
# Check the scenario that triggered the ban
# Add IP to whitelist or adjust scenario sensitivity
Issue: CrowdSec not parsing logs
Solution: Verify log format and permissions:
docker exec crowdsec cscli metrics
# Check "Acquisition Metrics" - should show parsed lines
# Ensure log files are readable
ls -la /home/ed/docker/traefik/logs/
Application-Specific Configurations
Services Requiring Special Handling
| Application | Special Requirement | Configuration Location |
|---|---|---|
| Home Assistant | Trusted proxy whitelist | configuration.yaml |
| qBittorrent | CSRF protection disable | Web UI > Options |
| Plex | Custom server URL | Settings > Network |
| Nextcloud | Trusted domains | config.php |
| Portainer | Edge agent configuration | Settings > Endpoints |
Common Port Reference
| Service | Default Port | Protocol |
|---|---|---|
| Plex | 32400 | HTTP |
| qBittorrent | 8080 | HTTP |
| Home Assistant | 8123 | HTTP |
| Authentik | 9000 | HTTP |
| CrowdSec LAPI | 8080 | HTTP |
| CrowdSec Bouncer | 8080 | HTTP |
| Portainer | 9443 | HTTPS |
| Nextcloud | 80 | HTTP |
Troubleshooting
404: Page Not Found
Symptoms: Traefik returns a generic 404 page
Common Causes:
- Network Mismatch: Container not on the
traefiknetwork - Typo in Host Rule: Domain in label doesn't match DNS entry
- Provider Config Not Loaded: File provider YAML has syntax errors
- Container Not Exposed: Missing
traefik.enable=truelabel
Debugging Steps:
# Verify container is on traefik network
docker network inspect traefik
# Check Traefik logs for provider errors
docker logs traefik
# Validate YAML syntax
yamllint dynamic/*.yaml
# Access Traefik Dashboard
https://traefik.dashboard.fullmetaled.com
400: Bad Request (Home Assistant)
Cause: Home Assistant rejects proxy headers
Solution: Add Traefik IP to trusted_proxies in HA configuration.yaml
403: Forbidden (CrowdSec)
Symptoms: Access denied with "Forbidden by CrowdSec" or blank 403 page
Common Causes:
- IP Banned: Your IP is in the CrowdSec ban list
- False Positive: Legitimate behavior triggered a detection scenario
- Bouncer Misconfiguration: API key mismatch
Debugging Steps:
# Check if your IP is banned
docker exec crowdsec cscli decisions list --ip <your_ip>
# Review recent bans
docker exec crowdsec cscli decisions list
# Check bouncer logs
docker logs crowdsec-bouncer
# Temporarily unban yourself
docker exec crowdsec cscli decisions delete --ip <your_ip>
502: Bad Gateway
Symptoms: Traefik can't reach the backend service
Common Causes:
- Wrong Backend URL: IP or port incorrect in service definition
- Network Unreachable: Firewall between Traefik and target
- Service Not Running: Backend container/application stopped
Debugging Steps:
# From Traefik container, test connectivity
docker exec -it traefik ping <backend_ip>
docker exec -it traefik wget -O- http://<backend_ip>:<port>
Infinite Redirect Loop (Authentik)
Cause: Mismatch between Authentik provider config and Traefik rule
Solution: Ensure External Host in Authentik matches Host() rule exactly:
- Traefik:
Host(\app.fullmetaled.com`)` - Authentik:
https://app.fullmetaled.com
Middleware Not Found
Symptoms: Log entry: middleware "authentik@file" does not exist
Causes:
- File Provider Not Loaded:
--providers.file.directorynot configured - Wrong Provider Suffix: Used
@dockerinstead of@file - YAML Syntax Error: Indentation issue in
headers.yaml
Verification:
# Check if file provider is active in Traefik dashboard
# HTTP > Middlewares section should show "authentik@file" and "crowdsec-bouncer@file"
TLS Certificate Issues
Symptoms: Browser shows "Traefik Default Cert" warning
Causes:
- Certificate Files Missing:
/certs/origin.pemor/certs/privatekey.pemnot mounted - File Provider Not Loaded:
tls.yamlnot being parsed - Domain Mismatch: Certificate CN/SAN doesn't include the requested domain
Verification:
# Check certificate details
openssl x509 -in certs/origin.pem -text -noout | grep -A1 "Subject Alternative Name"
Security Best Practices
1. Credential Management
- Never commit API keys or passwords to version control
- Use
.envfiles or Docker Secrets for sensitive values - Rotate credentials immediately if exposed
2. Network Segmentation
- Databases should never be on the
traefiknetwork - Use dedicated internal networks for backend communication
- Apply principle of least privilege to network attachments
3. Access Control
- Enable Authentik for all administrative interfaces
- Use strong BasicAuth passwords (generated via
htpasswd -nB) - Restrict Traefik dashboard access to internal IPs if possible
- Apply CrowdSec to public-facing services
4. Monitoring
- Review
access.logregularly for anomalous traffic patterns - Set up log aggregation (ELK/Graylog) for centralized analysis
- Monitor for 401/403 errors indicating unauthorized access attempts
- Review CrowdSec metrics weekly:
docker exec crowdsec cscli metrics
5. Container Hardening
security_opt:
- no-new-privileges:true
read_only: true # Where possible
cap_drop:
- ALL
6. CrowdSec Tuning
- Subscribe to the CrowdSec Community Blocklist for real-time threat intel
- Regularly update collections:
docker exec crowdsec cscli collections upgrade --all - Review and prune old decisions monthly
- Whitelist known-good IPs (monitoring systems, CDN providers)
Appendix: Complete Working Examples
Full Traefik Stack with Authentik and CrowdSec
See primary docker-compose.yml in Core Traefik Setup combined with CrowdSec Stack Deployment
Multi-Stack BookStack Deployment
See Multi-Network Container Isolation
External Service Matrix
| Service | File | Protected By | Notes |
|---|---|---|---|
| Plex | plex.yaml |
CrowdSec | Native auth sufficient |
| qBittorrent | qbittorrent.yaml |
Authentik + CrowdSec | Double protection |
| Home Assistant | home-assistant.yaml |
CrowdSec only | Mobile app auth incompatible |
Document Maintenance:
- Review quarterly or when upgrading Traefik major versions
- Update after adding new service types
- Validate all examples after Traefik updates
- Update CrowdSec collections monthly