Skip to content
James Brooks // thatnerdknows
all writing
// writing

CrowdSec IPS in front of Traefik

May 28, 2026 · #crowdsec #traefik #security #homelab

What you get: a lightweight intrusion-prevention system that reads your Traefik access logs, detects brute-force / scanning / exploit attempts against your published services, and blocks the source IPs — plus a crowd-sourced threat-intel community blocklist so you drop known-bad IPs before they even probe you. It’s the best ROI upgrade for “I exposed a few things and want them defended.”

The gotcha that will silently defeat you: if you sit behind Cloudflare, Traefik sees Cloudflare’s IPs as the client — so CrowdSec will happily ban Cloudflare (locking out everyone) while the real attacker sails through. Fixing that is the core of this guide.


1. Prereq — Traefik must log JSON with the real client IP

# traefik.yml
accessLog:
  filePath: /var/log/traefik/access.log
  format: json
  fields:
    headers:
      names:
        Cf-Connecting-Ip: keep      # <- keep Cloudflare's real-client header in the log

Keep Traefik’s ClientHost as-is (the Cloudflare edge IP) so any lan-or-cloudflare bypass guards still work — the real client is now available in the log as request_Cf-Connecting-Ip.

2. Install CrowdSec + the collections

If Traefik runs as a native binary (common on an LXC), install the apt package; if it’s in Docker, use the CrowdSec container. Then grab the Traefik/HTTP scenarios:

apt-get install -y crowdsec
cscli collections install crowdsecurity/traefik crowdsecurity/http-cve \
                          crowdsecurity/base-http-scenarios crowdsecurity/linux
# point the log acquisition at Traefik's access log
cat >/etc/crowdsec/acquis.d/traefik.yaml <<'EOF'
filenames: [/var/log/traefik/access.log]
labels: { type: traefik }
EOF
systemctl restart crowdsec
cscli metrics          # confirm it's parsing

3. THE FIX — make CrowdSec key on the real client IP (behind Cloudflare)

Out of the box CrowdSec bans evt.Meta.source_ip = ClientHost = the Cloudflare edge IP. Behind CF that’s wrong twice: it would ban Cloudflare (block your whole site) and never ban the attacker. Add a tiny enrichment parser that re-keys the source IP to the Cf-Connecting-Ip header:

# /etc/crowdsec/parsers/s02-enrich/cloudflare-real-ip.yaml
onsuccess: next_stage
name: yourname/cloudflare-real-source-ip
filter: "evt.Meta.log_type == 'http_access-log' && evt.Unmarshaled.traefik['request_Cf-Connecting-Ip'] != ''"
statics:
  - meta: source_ip
    expression: "evt.Unmarshaled.traefik['request_Cf-Connecting-Ip']"

LAN/direct hits have no Cf-Connecting-Ip header, so they’re skipped (unchanged). Verify with a real log line:

cscli explain --file /var/log/traefik/access.log --type traefik | grep -i source_ip
#  -> update evt.Meta.source_ip : 162.159.x.x  ->  73.x.x.x   (Cloudflare edge -> the real WAN IP) ✓

Now bans land on the actual attacker. Restart CrowdSec after adding the parser.

4. Enforce the bans — bouncers

  • At Traefik (per-request): the Traefik CrowdSec bouncer plugin checks each request against the local API and blocks/serves-a-challenge inline. Fastest to add, protects the published services.
  • At the perimeter (better): push decisions upstream so bad IPs never reach your box at all — to your firewall or to Cloudflare (challenge/block at the edge). See the Cloudflare note below.

5. Repeat offenders — escalate, don’t just re-ban

Default bans are short (~4 h) with no escalation, so the same IPs cycle back the moment the timer expires. Add escalation so persistence costs them:

cscli collections install crowdsecurity/repeat-offenders   # ramps 4h -> 24h -> 7d -> ... on re-ban

(or a custom profile with your own ladder). This is what actually clears “the same IPs keep reappearing.”

⚠️ Cloudflare note — the packaged bouncer is dead; roll your own sync

The stock crowdsec-cloudflare-bouncer only speaks Cloudflare’s legacy Firewall Rules API, which CF has frozen (10020 maintenance_mode) — it creates the IP list, then crashes making the rule. Don’t use it. Instead, on Cloudflare Free, use a small custom sync against the modern Lists + Rulesets APIs:

  • Create one custom IP List + one WAF custom rule (ip.src in $your_list) → action managed_challenge (false-positive-safe: a bad community entry only gets a solvable challenge, never a hard lockout).
  • A systemd timer pushes CrowdSec ban decisions into that list every ~60 s.
  • Whitelist your own WAN IP (and re-whitelist if it’s dynamic/residential and changes).
  • Use a scoped API token with Account → Filter Lists + Firewall/WAF + Zone Read — a DNS-only token can’t touch firewall rules.

Gotchas recap

  1. The Cloudflare real-IP re-key is mandatory behind CF — without it CrowdSec bans Cloudflare and misses the attacker. Add the s02-enrich parser, verify with cscli explain.
  2. Log JSON + keep Cf-Connecting-Ip in Traefik, or the parser has nothing to read.
  3. Escalate repeat offenders (crowdsecurity/repeat-offenders) — short flat bans just teach them to wait.
  4. The packaged Cloudflare bouncer is broken — sync to a modern Lists/Rulesets WAF rule yourself; whitelist your own WAN.

Written from a working deployment: CrowdSec (native apt) reading Traefik JSON logs on an LXC, a custom s02-enrich parser re-keying to Cf-Connecting-Ip, and a custom Cloudflare-Free sync (Lists + Rulesets → managed_challenge) after the packaged bouncer crashed on CF’s frozen legacy API.


Written by James Brooks — I run ThatNerdKnows (IT support + websites for small businesses). This is the deep end; if you’d rather just have it handled, that’s the day job.