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.

Web

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.yaml
  • docker/traefik/data/traefik.yml
  • docker/traefik/data/config.yml
  • docker/crowdsec/compose.yaml
  • docker/crowdsec/config/acquis.yaml
  • docker/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: true is 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: true exists in traefik.yml. If this is not strictly required, remove it. TLS verification bypass is not a harmless default.
  • api.debug: true and log.level: "DEBUG" are useful while tuning, but too chatty for steady-state production. They increase sensitive telemetry exposure and operational noise.
  • crowdsecurity/crowdsec:latest and fbonalair/traefik-crowdsec-bouncer:latest make 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 https is easier to reason about.
  • Log pipeline quality determines ban quality. If Traefik logging or acquis.yaml breaks, 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.md guidance to use https (not websecure) avoids a lot of self-inflicted outages.

What I’d do differently

  1. Pin all security-critical images to explicit versions and update on a controlled cadence.
  2. Change trustForwardHeader to false unless there is a demonstrated need, and explicitly trust only known upstream hops when needed.
  3. Remove insecureSkipVerify: true unless I can document exactly why it is required.
  4. Drop Traefik from DEBUG to INFO after validation and keep debug windows time-boxed.
  5. Put the Traefik dashboard behind an additional control layer (VPN and/or IP allowlist), not just basic auth.
  6. Explicitly attach security headers middleware on routers that should enforce it, then verify with response inspection.

References