Traefik - Modern Reverse Proxy
Traefik is the reverse proxy at the heart of the homelab's Docker infrastructure. It handles routing of all HTTP/HTTPS requests to the appropriate containers, with automatic SSL certificate management and security integration via CrowdSec.
Global Architecture
The infrastructure uses two distinct Traefik instances, each on its own network interface:
- traefik-public (192.168.1.2): Public services accessible from the Internet (*.tellserv.fr)
- traefik-private (192.168.1.3): Local services reserved for the internal network (*.local.tellserv.fr)
This separation provides:
- Network isolation: Private services are never exposed publicly
- Enhanced security: Differentiated security policies based on exposure
- Simplified management: Each instance has its own configuration and rules
Network Prerequisites
The VM hosting Traefik has two NICs (Network Interface Cards):
- NIC 1 (192.168.1.2): Public interface for traefik-public
- NIC 2 (192.168.1.3): Local interface for traefik-private
Common Elements to Both Instances
Image and Version
Both instances use the official Traefik v3 image:
image: traefik:v3
Docker Network
Both instances connect to the external Docker network traefik_network which enables communication with all containers to be proxied:
networks:
- traefik_network
This network must be created beforehand with:
docker network create traefik_network
SSL Certificate Management
Both instances use Let's Encrypt with the Cloudflare DNS challenge to automatically generate wildcard SSL certificates.
DNS Challenge Advantages:
- Wildcard certificates (*.tellserv.fr, *.local.tellserv.fr)
- No need for public HTTP exposure for validation
- Works even for internal services
Common Configuration:
environment:
- TZ=Europe/Paris
- CF_DNS_API_TOKEN=${CF_DNS_API_TOKEN}
The Cloudflare API token must have the following permissions:
- Zone / DNS / Edit
- Zone / Zone / Read
Docker Provider
Both instances use the Docker provider for automatic service discovery:
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
Traefik monitors containers and automatically creates routes based on Docker labels.
Dynamic Configuration
Each instance loads dynamic configuration files from a dedicated directory:
volumes:
- ./dynamic-public:/etc/traefik/dynamic:ro # For traefik-public
- ./dynamic-private:/etc/traefik/dynamic:ro # For traefik-private
These files allow defining middlewares, routers, and services without restarting Traefik.
Traefik Dashboard
Both instances expose their dashboard via a dedicated subdomain:
- traefik-public:
traefik-public.local.tellserv.fr - traefik-private:
traefik-private.local.tellserv.fr
The dashboard allows real-time visualization of:
- Active routers and their rules
- Detected services
- Applied middlewares
- Backend health status
Restart Policy
restart: unless-stopped
Containers automatically restart unless manually stopped.
Docker Host Access
extra_hosts:
- "host.docker.internal:host-gateway"
This configuration allows Traefik containers to access the Docker host via the name host.docker.internal, useful for proxying services running directly on the host.
traefik-public Instance
Role and Usage
The traefik-public instance manages all services accessible from the Internet:
- Public web applications
- Exposed APIs
- Authenticated services accessible from outside
Network Binding
ports:
- "192.168.1.2:80:80"
- "192.168.1.2:443:443"
Traefik listens only on IP 192.168.1.2, corresponding to the VM's first NIC.
Entry Points
Port 80 (HTTP):
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
Automatic HTTP to HTTPS redirection for all requests.
Port 443 (HTTPS):
websecure:
address: ":443"
http:
middlewares:
- crowdsec-bouncer@file
- secheaders@file
- ratelimit@file
transport:
respondingTimeouts:
idleTimeout: 300s
Three middlewares applied by default to all public services:
- crowdsec-bouncer: Blocking malicious IPs detected by CrowdSec
- secheaders: HTTP security headers (HSTS, CSP, etc.)
- ratelimit: Request rate limiting
Public Middlewares
File: dynamic-public/middlewares.yml
ratelimit:
ratelimit:
rateLimit:
average: 100
burst: 50
period: 1s
Allows an average of 100 requests/second with bursts up to 50 additional requests.
secheaders:
secheaders:
headers:
stsSeconds: 31536000
forceSTSHeader: true
Forces HSTS (HTTP Strict Transport Security) for 1 year, requiring browsers to always use HTTPS.
evasive:
evasive:
rateLimit:
average: 3
burst: 5
period: 1s
Strict rate limiting for sensitive endpoints (3 req/s average, 5 burst).
CrowdSec Integration
CrowdSec is a community-based intrusion detection and prevention system. Traefik-public integrates the CrowdSec bouncer to automatically block malicious IPs.
CrowdSec Middleware (applied to websecure):
middlewares:
- crowdsec-bouncer@file
The bouncer queries the local CrowdSec API to check if the source IP is banned. On match, the request is blocked with HTTP 403.
SSL Certificates
Certificate storage:
volumes:
- ./letsencrypt-public:/letsencrypt
ACME configuration in traefik-public.yml:
certificatesResolvers:
cloudflare:
acme:
email: your-email@example.com
storage: /letsencrypt/cloudflare_acme.json
keyType: EC256
dnsChallenge:
provider: cloudflare
resolvers:
- "1.1.1.1:53"
- "8.8.8.8:53"
Certificates are automatically renewed 30 days before expiration.
Logging
log:
level: DEBUG
filePath: "/var/log/traefik/traefik.log"
accessLog:
filePath: "/var/log/traefik/access.log"
format: json
Logs stored in /var/log/traefik/ on the Docker host:
- traefik.log: Traefik system logs (startup, errors, reloads)
- access.log: Access logs in JSON format (HTTP requests)
Docker Provider Configuration
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
network: traefik_network
- exposedByDefault: false: Containers are not automatically exposed, Traefik labels must be explicitly added
- network: traefik_network: Traefik will use this network to communicate with containers
Docker Labels Example
To expose a service via traefik-public:
services:
myapp:
image: myapp:latest
labels:
- "traefik.enable=true"
- "traefik.http.routers.myapp.rule=Host(`app.tellserv.fr`)"
- "traefik.http.routers.myapp.entrypoints=websecure"
- "traefik.http.routers.myapp.tls.certresolver=cloudflare"
- "traefik.http.services.myapp.loadbalancer.server.port=80"
networks:
- traefik_network
traefik-private Instance
Role and Usage
The traefik-private instance manages all services reserved for the local network:
- Administration interfaces (Proxmox, Cockpit)
- Internal dashboards
- Monitoring services
- Development tools
Network Binding
ports:
- "192.168.1.3:80:80"
- "192.168.1.3:443:443"
Traefik listens only on IP 192.168.1.3, corresponding to the VM's second NIC.
Entry Points
Port 80 (HTTP):
weblocal:
address: ":80"
http:
redirections:
entryPoint:
to: local
scheme: https
Automatic HTTP to HTTPS redirection (to local entrypoint).
Port 443 (HTTPS):
local:
address: ":443"
http:
middlewares:
- localonly@file
A single middleware applied by default: localonly which restricts access to local IPs.
Private Middlewares
File: dynamic-private/middlewares.yml
localonly:
localonly:
ipWhiteList:
sourceRange:
- "127.0.0.1/32"
- "192.168.1.0/24"
- "100.64.0.0/10"
- "172.18.0.0/16"
Whitelist of allowed IPs:
- 127.0.0.1/32: Localhost
- 192.168.1.0/24: Main LAN network
- 100.64.0.0/10: Tailscale network (VPN)
- 172.18.0.0/16: Internal Docker network
Any request from an IP outside these ranges is rejected with HTTP 403.
ratelimit, secheaders, evasive:
Identical to traefik-public, available to be applied as needed to specific services.
SSL Certificates
Certificate storage:
volumes:
- ./letsencrypt-private:/letsencrypt
ACME configuration in traefik-private.yml:
certificatesResolvers:
cloudflare:
acme:
email: your-email@example.com
storage: /letsencrypt/cloudflare_acme.json
keyType: EC256
dnsChallenge:
provider: cloudflare
Although services are local, they benefit from valid SSL certificates thanks to DNS challenge.
Logging
log:
level: DEBUG
filePath: "/var/log/traefik-local/traefik.log"
accessLog:
filePath: "/var/log/traefik-local/access.log"
format: json
Logs stored in /var/log/traefik-local/ on the Docker host.
Docker Provider Configuration
Identical to traefik-public:
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
network: traefik_network
Exposed Services
Static configuration files in dynamic-private/:
cockpit.yml: Proxying Cockpit (system administration web interface) proxmox.yml: Proxying Proxmox interface
These files define routers and services for applications not running in Docker.
Docker Labels Example
To expose a service via traefik-private:
services:
monitoring:
image: grafana/grafana:latest
labels:
- "traefik.enable=true"
- "traefik.http.routers.monitoring.rule=Host(`grafana.local.tellserv.fr`)"
- "traefik.http.routers.monitoring.entrypoints=local"
- "traefik.http.routers.monitoring.tls.certresolver=cloudflare"
- "traefik.http.services.monitoring.loadbalancer.server.port=3000"
networks:
- traefik_network
Security and Best Practices
Public/Private Separation
- Never expose administration services via traefik-public
- Always verify the entrypoint used in Docker labels
- Prefer traefik-private for anything that doesn't need to be public
Security Middlewares
- CrowdSec: Active only on traefik-public, blocks automated attacks
- localonly: Applied by default on traefik-private
- ratelimit: Basic anti-DDoS protection
- secheaders: Browser-side security enhancement
Certificate Management
- Automatic rotation: Let's Encrypt renews certificates every 90 days
- Backup: Regularly backup
cloudflare_acme.jsonfiles - Monitoring: Check logs to detect renewal failures
Logging and Monitoring
- Accessible logs: Mounted as volumes on the host for analysis
- JSON format: Facilitates parsing and integration with monitoring tools
- DEBUG level: Useful for troubleshooting, can be reduced in production
Configuration Limitations
While functional and secure, this architecture has certain limitations to be aware of:
Shared Docker Network
Issue: Both Traefik instances (public and private) use the same Docker network (traefik_network). This means all containers connected to this network can potentially communicate with each other, whether exposed publicly or locally.
Impact:
- A container exposed via traefik-public can technically access a container exposed via traefik-private
- Network isolation is incomplete, relying only on IP bindings (192.168.1.2 vs 192.168.1.3)
Possible Improvement:
- Create two distinct Docker networks:
traefik_public_networkandtraefik_private_network - Connect each Traefik instance only to its dedicated network
- Ensure complete network isolation at the Docker level
Lack of VLAN Segmentation
Issue: Both VM NICs share the same physical network (192.168.1.0/24) without VLAN segmentation.
Impact:
- The NIC for traefik-private (192.168.1.3) technically has Internet access via the network gateway, when it doesn't need it
- No network isolation at L2/L3 level between public and private interfaces
- In case of compromise, an attacker could potentially pivot between both networks
Possible Improvement:
- Public VLAN: Place the traefik-public NIC (192.168.1.2) in a VLAN with Internet access
- Private VLAN: Place the traefik-private NIC (192.168.1.3) in an isolated VLAN without Internet access
- Configure strict firewall rules between VLANs
- This segmentation would significantly strengthen isolation and limit attack surface
Docker Socket Access
Issue: Both Traefik instances have direct and complete access to the Docker socket (/var/run/docker.sock). The Docker socket is Docker's administration API, giving full control over the host.
Security Impact:
- A compromised Traefik container could control all host containers
- Possibility of privilege escalation (launching containers in privileged mode, mounting sensitive volumes, etc.)
- Read-only access (
ro) limits damage, but still allows extracting sensitive information (environment variables, secrets, etc.)
Possible Improvement:
- Use a Docker socket proxy like Tecnativa/docker-socket-proxy
- This proxy allows fine-grained filtering of allowed operations (e.g., only read containers and their labels)
- Reduce attack surface by limiting API access to endpoints strictly necessary for Traefik
Configuration example:
docker-socket-proxy:
image: tecnativa/docker-socket-proxy
environment:
CONTAINERS: 1 # Allow container reading
NETWORKS: 1 # Allow network reading
SERVICES: 0 # Deny access to Swarm services
TASKS: 0 # Deny access to Swarm tasks
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
These improvements are not critical for a homelab but would be strongly recommended in production environments.