Skip to main content

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:

  1. crowdsec-bouncer: Blocking malicious IPs detected by CrowdSec
  2. secheaders: HTTP security headers (HSTS, CSP, etc.)
  3. 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 protected]
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 protected]
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.json files
  • 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_network and traefik_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.

Resources​