CrowdSec
CrowdSec watches Traefik’s access logs and decides which IPs to ban for attack patterns (brute force, CVE probes, path traversal). On its own it detects but doesn’t block — blocking is added separately, in layers you choose.
Verified 29 Jul 2026 against v1.7.8
Not exercised: SSH and host log sources — Debian 13 keeps no `auth.log`, so only the proxy layer feeds the engine.
Needs: Traefik running.
Installation
Section titled “Installation”The required baseline — detection only, blocks nothing yet.
-
From
core/crowdsec/:Terminal window cp .env.example .env -
Find the GID that owns Traefik’s log files, and set it as
CROWDSEC_LOG_GIDin.env:Terminal window stat -c '%g' ../traefik/volumes/logs/access.log -
Start:
Terminal window docker compose up -d
Verify
Section titled “Verify”docker exec crowdsec cscli lapi statusdocker exec crowdsec cscli metrics show acquisitionExpect “You can successfully interact with Local API” and a row for file:/var/log/traefik/access.log with lines_read > 0.
The three layers, and what each one covers
Section titled “The three layers, and what each one covers”They are separate components with one thing in common: the engine decides, nothing else does. It writes decisions to a list; each enforcement layer polls that list and applies it in its own place. A decision with no enforcer attached does nothing at all.
| Where it acts | Catches | |
|---|---|---|
| Engine | reads the proxy’s access log | decides — blocks nothing itself |
| Proxy plugin | inside Traefik, per request | HTTP attacks; answers 403 and sees the decrypted request |
| Firewall bouncer | nftables on the host | everything, on every port — the packet is dropped before a connection exists |
The proxy plugin has the better view: it knows the URL and can say why it refused. The firewall bouncer has the wider one — SSH, port scans and anything not behind the proxy never reach Traefik at all.
Running both is not redundancy. Measured here: a decision reaches the kernel in about 15 seconds and clears again in about 10, while the proxy plugin acts on its own polling cycle in between.
What the firewall layer inherits the moment you enable it
Section titled “What the firewall layer inherits the moment you enable it”The engine subscribes to CrowdSec’s community blocklist from the start. On this host that had quietly accumulated roughly 15,800 addresses — known-bad, with nothing enforcing them, because Phase 1 only decides.
Alongside them sat one the engine had found itself: an address probing this host for common exploit paths, eleven events, banned three hours earlier. Until the firewall layer existed, that ban applied only to HTTP through the proxy.
What it can and cannot see
Section titled “What it can and cannot see”The two protective layers in this Blueprint do not add up — they take turns, and that determines where it is worth attaching.
Measured here: 120 requests against a VPN-only host moved CrowdSec’s counter by
3. Behind acc-tailscale or acc-private, the engine sees close to nothing —
which is not a problem, because nothing got through either. It becomes worth its
keep the moment an application is public.
The WAF layer is narrower than the name suggests
Section titled “The WAF layer is narrower than the name suggests”The optional AppSec engine inspects each request against rule sets. Measured against a public router:
| Request | Result |
|---|---|
?id=1' OR '1'='1 |
passes |
?q=<script>alert(1)</script> |
passes |
?f=../../../../etc/passwd |
passes |
/vendor/phpunit/…/eval-stdin.php |
blocked |
The shipped rules are virtual patches for named CVEs plus a small generic set — not a general injection filter. It stops the mass scanning that finds any public host within minutes of a certificate being issued, which is most of what arrives uninvited. It will not stop an attack crafted against your application’s own parameters, and it is no reason to relax anything about input handling.
The upside of that narrowness: far fewer false positives than a full WAF, which makes it a reasonable default for a public app.
Going further
Section titled “Going further”Phase 1 above is detection only. Two more layers, each independently optional, add blocking.
Rejects requests from banned IPs with a 403, inside Traefik.
-
Generate a key:
Terminal window docker exec crowdsec cscli bouncers add traefik-bouncer -
In
core/traefik/.env, addCROWDSEC_BOUNCER_KEY=<key>. -
Uncomment the plugin block in
ops/templates/traefik.yml.tmpland thesec-crowdsecmiddleware inops/templates/dynamic/integrations.yml.tmpl, then:Terminal window bash ops/scripts/render.shdocker compose up -d --force-recreate traefik -
Add
sec-crowdsec@fileto at least one router’s middleware list, then recreate that app:- "traefik.http.routers.${COMPOSE_PROJECT_NAME}.middlewares=sec-crowdsec@file,${APP_TRAEFIK_ACCESS}@file,${APP_TRAEFIK_SECURITY}@file"
Verify:
docker exec crowdsec cscli bouncers list# Expect an IP address and a recent "Last API pull"Drops packets from banned IPs at the host firewall — every port, not just HTTP. Runs as a host package, not a container.
Worth it mainly if SSH is reachable from the public internet (Traefik never sees SSH brute force). Skip if SSH is Tailscale-only.
-
Install and register:
Terminal window sudo apt install crowdsec-firewall-bouncer # Debian 13, from Debian maindocker exec crowdsec cscli bouncers add firewall-bouncer -
Edit
/etc/crowdsec/bouncers/crowdsec-firewall-bouncer.yaml.local— never the base.yaml, the package manages that:api_url: http://127.0.0.1:8080/api_key: <key from above>mode: nftables -
Start:
Terminal window sudo systemctl enable --now crowdsec-firewall-bouncer
Verify:
sudo systemctl status crowdsec-firewall-bouncerdocker exec crowdsec cscli bouncers listTroubleshooting
Section titled “Troubleshooting”“The site does not load for me” — check this first, not last. Once the firewall layer is running, the most likely cause of one person being unable to reach a service is that their address is on a blocklist. The packet is dropped in the kernel, so there is no log line anywhere to tell you — not in the proxy, not in the application.
docker exec crowdsec cscli decisions list --ip <their address>docker exec crowdsec cscli decisions delete --ip <their address>Most of those decisions did not come from your server. The engine subscribes to CrowdSec’s community blocklist, which on a running host means around 15,000 addresses other installations reported. That is what the subscription delivers, and it also means a shared CGNAT address, a VPN exit or a compromised neighbour can take a legitimate visitor with it.
The bouncer never polls — cscli bouncers list shows no Last API pull.
Loading the plugin is not enough: the polling loop starts only once a router
actually uses the middleware. Attach it to one, then check again. There is no
error message for this.
A ban you set does not seem to work when you test it from the server itself — it is the test that is wrong. A request made on the host arrives through the Docker bridge, so the proxy records the gateway as the client and the address you banned never appears as a sender. Read the address out of the access log, ban that one, and have the real client retry.
AppSec refuses every request — the engine is unreachable and the profile is
fail-closed. Check that something answers on 7422 from the proxy container; a
401 is correct there, connection refused is not.
A legitimate IP got banned. Start narrow, and check the result rather than assuming it:
docker exec crowdsec cscli decisions list --ip <ip> # what exists, and from which origindocker exec crowdsec cscli decisions delete --ip <ip>docker exec crowdsec cscli decisions list --ip <ip> # expect nothingIf the decision comes back after you delete it, deleting harder is not the fix. Find out where it comes from:
docker exec crowdsec cscli decisions list --ip <ip> -o json # origin, scenario, durationdocker exec crowdsec cscli alerts list --ip <ip> # what triggered itAn origin of crowdsec means your own engine decided it and will decide it
again from the same traffic. lists or CAPI means it arrived with the
community blocklist. A local re-detection needs a whitelist entry, not a
deletion.
A ban that keeps coming back is a different problem — the engine is still seeing the behaviour that caused it. A whitelist entry stops decisions being created at all, which is the right tool for your own monitoring, office address or a known crawler.
Full operational reference — health checks, whitelisting, false positives, SSH detection, geo-blocking, emergency procedures: core/crowdsec/docs/runbook.md.
Repository files
Section titled “Repository files”core/crowdsec/README.md— full setup for all three phasescore/crowdsec/docs/runbook.md— day-to-day operationscore/crowdsec/docs/firewall-bouncer.md— Phase 3 in depth, including SSH detection
© 2026 SecDockBlueCode Apache-2.0Site content licence not yet decided