Skip to content
IT related

Traefik V3 Configuration guide

Updated 7 min read

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

  1. traefik.docker.network=traefik: When a container is on multiple networks, this label explicitly tells Traefik which interface to route to
  2. Database Isolation: The database container has no connection to the traefik network, preventing lateral movement in case of proxy compromise
  3. Service Resolution: Docker's internal DNS allows bookstack to resolve bookstack_db via 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:

  1. Navigate to Settings > Server > Network
  2. 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:

  1. Tools > Options > Web UI
  2. Disable "Enable Cross-site Request Forgery (CSRF) protection"
  3. 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:

  1. Be named authentik (or update the address URL accordingly)
  2. Be on the traefik network
  3. 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:

  1. Applications > Providers > Create → Select "Proxy Provider"
  2. External Host: Must match the Traefik Host() rule exactly (e.g., https://qbit.fullmetaled.com)
  3. Forward auth (single application): Select this mode
  4. Create Application and link to this provider
  5. 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.yaml to use poll_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:

  1. Network Mismatch: Container not on the traefik network
  2. Typo in Host Rule: Domain in label doesn't match DNS entry
  3. Provider Config Not Loaded: File provider YAML has syntax errors
  4. Container Not Exposed: Missing traefik.enable=true label

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:

  1. IP Banned: Your IP is in the CrowdSec ban list
  2. False Positive: Legitimate behavior triggered a detection scenario
  3. 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:

  1. Wrong Backend URL: IP or port incorrect in service definition
  2. Network Unreachable: Firewall between Traefik and target
  3. 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:

  1. File Provider Not Loaded: --providers.file.directory not configured
  2. Wrong Provider Suffix: Used @docker instead of @file
  3. 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:

  1. Certificate Files Missing: /certs/origin.pem or /certs/privatekey.pem not mounted
  2. File Provider Not Loaded: tls.yaml not being parsed
  3. 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 .env files or Docker Secrets for sensitive values
  • Rotate credentials immediately if exposed

2. Network Segmentation

  • Databases should never be on the traefik network
  • 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.log regularly 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
IT related RSS feed Published with Oblish

Keyboard shortcuts

/
Search
[
Previous page
]
Next page
?
Show this list
Esc
Close