Hardening Traefik with CrowdSec forwardAuth in a Homelab Reverse-Proxy Stack
Practical homelab guide to wire Traefik forwardAuth with CrowdSec, validate it, and handle the security tradeoffs before production.
TL;DR
This stack hardens Traefik by routing traffic on the https entrypoint through a CrowdSec forwardAuth bouncer, backed by Traefik access-log ingestion. It works, but there are important tradeoffs: trustForwardHeader: true, insecureSkipVerify: true, debug logging, and :latest tags on CrowdSec components all need tightening before this is production-grade.
Everything below is grounded in these homelab files:
docker/traefik/compose.yamldocker/traefik/data/traefik.ymldocker/traefik/data/config.ymldocker/crowdsec/compose.yamldocker/crowdsec/config/acquis.yamldocker/traefik/TRAEFIK_GUIDE.md
Why this pattern
In this homelab, Traefik is the front door (80/443) and CrowdSec handles detection and decisions. The key design choice is to enforce CrowdSec checks at the Traefik https entrypoint via forwardAuth, not router by router. That is less error-prone in day-to-day operations.
Concrete walkthrough
1) Define the CrowdSec forwardAuth middleware
From docker/traefik/data/config.yml:
http:
middlewares:
crowdsec-bouncer:
forwardAuth:
address: http://bouncer-traefik:8080/api/v1/forwardAuth
trustForwardHeader: true
This points Traefik to the bouncer container over the shared Docker proxy network.
2) Apply that middleware on the HTTPS entrypoint
From docker/traefik/data/traefik.yml:
entryPoints:
https:
address: ':443'
http:
middlewares:
- crowdsec-bouncer@file
This is the important part. With entrypoint-level middleware, every HTTP router on https gets the CrowdSec auth check by default.
3) Make sure CrowdSec can actually parse Traefik logs
From docker/crowdsec/compose.yaml:
services:
crowdsec:
environment:
COLLECTIONS: 'crowdsecurity/linux crowdsecurity/traefik'
volumes:
- ../traefik/logs:/var/log/traefik/:ro
bouncer-traefik:
environment:
CROWDSEC_AGENT_HOST: crowdsec:8080
From docker/crowdsec/config/acquis.yaml:
filenames:
- /var/log/traefik/*
labels:
type: traefik
If CrowdSec cannot ingest and classify Traefik logs, forwardAuth has far less decision context.
4) Keep dashboard exposure constrained
From docker/traefik/compose.yaml, the dashboard is already on HTTPS with basic auth middleware and Cloudflare cert resolver:
- 'traefik.http.routers.traefik-secure.entrypoints=https'
- 'traefik.http.routers.traefik-secure.middlewares=traefik-auth'
- 'traefik.http.routers.traefik-secure.tls=true'
- 'traefik.http.routers.traefik-secure.tls.certresolver=cloudflare'
- 'traefik.http.routers.traefik-secure.service=api@internal'
That is a good baseline, but basic auth alone is weak for a high-value admin surface.
5) Deploy and verify
# Bring up Traefik
cd /Users/tucker/projects/homelab/docker/traefik
docker compose up -d
# Bring up CrowdSec + bouncer
cd /Users/tucker/projects/homelab/docker/crowdsec
docker compose up -d
Operational checks for this setup:
# Traefik should show routers/middlewares/providers loading
docker logs traefik | tail -n 200
# CrowdSec should show acquis/collection activity
docker logs crowdsec | tail -n 200
# Bouncer health and connection to crowdsec agent
docker logs bouncer-traefik | tail -n 200
From TRAEFIK_GUIDE.md, keep label conventions consistent (https entrypoint, correct proxy network, correct service port). Entrypoint typos are one of the fastest ways to break both routing and protection.
Security notes
trustForwardHeader: trueis a real tradeoff. It can be required for upstream context, but it increases header-spoofing risk if requests can arrive from untrusted intermediaries.serversTransport.insecureSkipVerify: trueexists intraefik.yml. If this is not strictly required, remove it. TLS verification bypass is not a harmless default.api.debug: trueandlog.level: "DEBUG"are useful while tuning, but too chatty for steady-state production. They increase sensitive telemetry exposure and operational noise.crowdsecurity/crowdsec:latestandfbonalair/traefik-crowdsec-bouncer:latestmake upgrades nondeterministic. Traefik is pinned (v3.6.5), but these two are not.- Docker socket is mounted read-only in Traefik (
/var/run/docker.sock:ro), which is better than RW, but still a high-impact mount if Traefik is compromised. - Dashboard auth is currently basic auth. Add network-level restriction (VPN or IP allowlist) in front of it.
Lessons learned
- Entrypoint enforcement is cleaner than per-router drift. One middleware binding on
httpsis easier to reason about. - Log pipeline quality determines ban quality. If Traefik logging or
acquis.yamlbreaks, detection quality drops fast. - Security hardening is less about adding one tool and more about eliminating weak defaults around it.
- Operational consistency matters: this repo’s
TRAEFIK_GUIDE.mdguidance to usehttps(notwebsecure) avoids a lot of self-inflicted outages.
What I’d do differently
- Pin all security-critical images to explicit versions and update on a controlled cadence.
- Change
trustForwardHeadertofalseunless there is a demonstrated need, and explicitly trust only known upstream hops when needed. - Remove
insecureSkipVerify: trueunless I can document exactly why it is required. - Drop Traefik from
DEBUGtoINFOafter validation and keep debug windows time-boxed. - Put the Traefik dashboard behind an additional control layer (VPN and/or IP allowlist), not just basic auth.
- Explicitly attach security headers middleware on routers that should enforce it, then verify with response inspection.
References
- Traefik forwardAuth middleware docs: https://doc.traefik.io/traefik/middlewares/http/forwardauth/
- Traefik entrypoint docs (default middlewares on entrypoints): https://doc.traefik.io/traefik/routing/entrypoints/