Best practices
CrowdSec + Traefik: IDS/IPS for web apps, no appliance
Full configuration, detection scenarios, community blocklists and how to avoid false positives.
Published · Updated · 5 min read
Key takeaways
- CrowdSec detects behaviour from logs, and the Traefik bouncer enforces the decision before the request reaches your application.
- Detection (Local API + scenarios) and blocking (bouncer) are separate components — you can verify and debug them separately.
- The community blocklist is bidirectional: you receive signals and you also send them. It is a data decision, not just a switch.
- Most false positives come from legitimate crawlers, modern app prefetching and the bouncer's own 403 responses.
- Test every scenario change with
cscli explainboth ways: what must be ignored and what must still trigger.
What CrowdSec is and why it can replace an IDS/IPS appliance
An IDS (Intrusion Detection System) watches traffic and flags hostile behaviour; an IPS (Intrusion Prevention System) goes one step further and blocks it. For web applications this traditionally meant a dedicated appliance or a commercial WAF in front of the servers. If you already run a reverse proxy such as Traefik, you have everything you need for a software, open-source alternative that runs next to your applications.
CrowdSec is an open-source, behaviour-based detection engine. It reads logs (from Traefik, Nginx, SSH, the operating system), turns them into events through parsers and runs them through scenarios — *leaky bucket* rules that count events over a time window. When a bucket overflows (for example, too many 404s on distinct paths from one IP), CrowdSec issues a decision: ban, captcha or another remediation, with a duration.
The difference from a classic firewall: CrowdSec does not look at packets but at what the client does inside your application. That makes it a good fit for vulnerability scans, brute force against login forms, aggressive crawling and exploitation of known CVEs.
Architecture: Traefik, the Local API and the bouncer
To understand where things can go wrong, it helps to separate the three roles:
| Component | Role | What to check when it fails |
|---|---|---|
| Traefik (access log) | Data source: every HTTP request becomes a log line | The log is written, has the expected format and is mounted into the CrowdSec container |
| CrowdSec Security Engine + Local API (LAPI) | Parses logs, runs scenarios, stores decisions | cscli metrics shows lines read and parsed; cscli decisions list |
| Bouncer (Traefik plugin) | Middleware that queries LAPI and blocks the request | The API key is valid; cscli bouncers list shows a recent pull |
A request then flows: client → :443 entrypoint → bouncer middleware → router → application. The bouncer can run in live mode (queries LAPI for each new IP, with a cache) or stream mode (periodically downloads the full decision list and decides locally). Stream mode adds less latency per request, but a new decision only takes effect at the next pull.
Step-by-step setup for Traefik v3
- Enable the access log in Traefik's static configuration (
accessLogwithfilePath) and mount the log directory read-only into the CrowdSec container. - Install the right collection:
cscli collections install crowdsecurity/traefikbrings the parser and the basic HTTP scenarios. Add web CVE collections if you expose popular applications. - Declare the source in the acquisition file (
acquis.yaml): the file path and the labeltype: traefik, so the logs reach the right parser. - Generate a bouncer key:
cscli bouncers add traefik-bouncer. The key is shown once — keep it in a secret, not in code. - Declare the plugin in Traefik's static configuration (the plugins section) and create a middleware in the dynamic configuration with the LAPI address, the key and the mode (live or stream).
- Attach the middleware to the
websecureentrypoint or to each router. On the entrypoint it is harder to forget a new site. - Verify end to end: add a test decision for your own IP (
cscli decisions add --ip X.X.X.X --duration 5m), confirm you get a 403, then delete it.
Detection scenarios: what they catch and how to write your own
Hub scenarios cover the common patterns: probing of sensitive paths (/.env, /.git/config, wp-config.php), WordPress scanning, login brute force, aggressive crawling of non-static pages and known exploits for popular CVEs. Layer 7 flood detection lives in separate collections that are worth enabling only after you have tested them against your real traffic.
A custom scenario is a YAML file with a filter (an expression on event fields), a grouping key, a capacity and a leak speed. Two practical rules:
- Always group by something — usually the source IP (
groupby: evt.Meta.source_ip). A scenario without grouping puts every event into one shared bucket and may ban whichever IP fills it first, letting everyone else through. - Calibrate thresholds on real data, not intuition: look at how many requests a legitimate visitor makes in the worst case and set the capacity above that, with margin.
The community blocklist and the AppSec component (WAF)
Through the Central API (CAPI) your instance receives a community blocklist — IPs reported by other installations for similar behaviour — and in turn sends the signals it detects. The value is the network effect: a scanner that has already hit thousands of other servers can be blocked before it reaches you.
The AppSec component adds WAF capabilities: it inspects the request before it reaches the backend and applies virtual patching rules for known vulnerabilities. The Traefik plugin can query it alongside the decision check. One detail to watch: requests refused by AppSec appear in the access log as proxy responses, not application responses, so they will not automatically feed log-based scenarios — check separately how repeated AppSec blocks turn into a ban decision.
How to avoid false positives
An IPS that bans real customers is worse than one that misses a scanner. The classic sources of false positives:
- Legitimate crawlers. Googlebot or Bingbot may request many distinct URLs. Do not identify them by User-Agent — that is trivially forged. Verify the operator via forward-confirmed reverse DNS or published IP ranges, and use the good-actors whitelist collection.
- The amplification loop. CrowdSec reads the Traefik log, including the 403 responses produced by the bouncer itself. A banned IP that keeps browsing generates 403s, probing scenarios count them, and the ban renews forever. Exclude, via a parser, responses produced by the proxy rather than the application.
- Prefetching and modern apps. SPA frameworks may fire dozens of distinct requests when a page loads, with no user action. To a crawling scenario this looks like a robot.
- Clients that cancel requests. A mobile browser closing connections is not a slowloris attack.
The working tool is cscli explain --file line.log --type traefik: it shows step by step which parser processed the line and which scenarios it reached. Test both ways — the legitimate line must stay untouched, and the attack request must still trigger.
Fail-open or fail-closed: what happens when LAPI goes down
When the bouncer cannot reach the Local API it has two options: let traffic through (fail-open, availability before protection) or refuse it (fail-closed, protection before availability). Fail-closed sounds safer, but it turns any CrowdSec problem — a slow database, an invalid key after a reinstall, a restarted container — into a full outage of your sites. Choose deliberately, monitor the bouncer's last sync, and do not set the update interval shorter than the real duration of a full pull, or requests can pile up under load.
If CrowdSec protects essential services, tie it into the rest of the technical controls NIS2 requires — see NIS2 Art. 21: the technical controls and Linux server hardening with CIS.
Frequently asked questions
+Is CrowdSec an IDS or an IPS?
Both, depending on configuration. The detection engine alone is an IDS: it analyses logs and produces decisions. Combined with a bouncer (for example the Traefik plugin), decisions are enforced and requests are blocked, so it becomes an IPS.
+Does the bouncer slow the site down?
In stream mode the bouncer decides from a local cache, so the per-request cost is small. In live mode the first request from a new IP waits for a Local API answer. Measure on your own infrastructure before choosing.
+Do I have to enable the community blocklist?
No. It brings IPs already known to be hostile, but it means sending your own signals to CrowdSec. Assess the decision from a GDPR perspective and document it.
+How do I unblock a client banned by mistake?
With cscli decisions delete --ip X.X.X.X. Then find the cause with cscli alerts list and cscli explain so it does not happen again — often it is a legitimate crawler or the loop of 403s produced by the bouncer.
+Does CrowdSec replace a WAF?
Log-based scenarios react after requests have been made. The AppSec component adds inspection before the backend and virtual patching, so it covers a good part of a WAF's role, but its rules must be maintained and tested like any WAF.
Official sources
Want to see how this looks in the platform?
Defneo collects NIS2 and AI Act evidence from the systems already running. Start with the free Community Edition or ask for a demo.
This article is for information only and is not legal advice.
Related articles
- Best practicesLinux server hardening in 2026: the CIS checklist explainedThe 20 CIS controls that really matter, how to verify them automatically and what evidence to keep for audit.
- NIS2NIS2 Art. 21: the 10 measures, translated into technical controlsWhat “risk management”, “supply chain security” or “cyber hygiene” concretely mean on a real server — measure by measure, with the evidence you should keep.
- ThreatsAnatomy of a ransomware attack in Romania: from VPN to ransomA stage-by-stage reconstruction of a typical attack, built from public patterns, and the 4 controls that would have stopped it.