CrowdSec IPS in front of Traefik
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)→ actionmanaged_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
- The Cloudflare real-IP re-key is mandatory behind CF — without it CrowdSec bans Cloudflare and misses the
attacker. Add the
s02-enrichparser, verify withcscli explain. - Log JSON + keep
Cf-Connecting-Ipin Traefik, or the parser has nothing to read. - Escalate repeat offenders (
crowdsecurity/repeat-offenders) — short flat bans just teach them to wait. - 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.