How to Install CrowdSec as a Community WAF
Table of contents
- Key takeaways
- Why the switch from fail2ban is worth it
- Installing the agent
- Collections: work saved from day one
- Telling the agent what to read
- Diagnosis: what to look at in the first days
- Bouncers: from detection to effective blocking
- Turning on the WAF: AppSec and virtual patching
- Captcha remediation: not everything is a ban
- The real value of the community blocklist
- Monitoring
- Whitelists and common mistakes
- When CrowdSec does not pay off
- My recommendation
- Frequently asked questions
- How do I know if CrowdSec is actually blocking traffic?
- Do I need Traefik to use CrowdSec?
- Is CrowdSec's community blocklist free?
- Sources
Tested with CrowdSec 1.8.1 · firewall bouncer 0.0.36 · Traefik 3.7 · Debian 13.6 · verified
Updated: 2026-09-16
CrowdSec 1.8.1 replaces fail2ban by separating detection (agent plus LAPI) from blocking (bouncers). Install the agent with the official script on Debian or Ubuntu and review the acquisition the installer generates. Then add the Traefik or firewall bouncer, the AppSec WAF with its two collections and, optionally, Cloudflare Turnstile captcha.
CrowdSec, the modern evolution of fail2ban, installs on Debian or Ubuntu with the official script (curl -s https://install.crowdsec.net | sudo sh followed by sudo apt install crowdsec). The current stable release is 1.8.1, from September 3, 2026. After that you enable the right collections and add at least one bouncer, the component that queries blocking decisions and enforces them, for example in Traefik or the firewall. That bouncer is what turns plain detection into real protection.
CrowdSec[1] is a community WAF, that is, a web application firewall: the layer that filters malicious HTTP requests before they reach your app. It decouples detection from blocking, inspects each request with its AppSec component, and leans on a blocklist fed by the installations that share their detections. This guide walks through installation, Traefik integration and the WAF, explaining why each piece exists.
I updated this guide on September 16, 2026 and re-ran its steps on an arm64 machine. I tested the package in a Debian 13.6 container with systemd and plugin 1.7.1 on Traefik 3.7.13. Other checks (the AppSec startup failure, force_inotify, latency and solving the captcha) ran in a Docker stack with the crowdsecurity/crowdsec:v1.8.1 image. The outputs below come from those runs.
Key takeaways
- CrowdSec separates detection (agent + LAPI) from blocking (bouncers): the same decision source can feed an HTTP bouncer in Traefik and a network bouncer in iptables at the same time.
- The 1.8 installer detects your services and writes their acquisition to
/etc/crowdsec/acquis.d/; whatever it misses, such as Traefik logs, you add yourself with the right label. - The WAF (the AppSec component) needs two collections: with
appsec-virtual-patchingalone, CrowdSec does not start. - Captcha remediation (Cloudflare Turnstile) avoids banning legitimate users in brute-force scenarios.
- The community blocklist delivered 15,000 IPs to the test installation on its first pull.
- On a hobby VPS with a single layer to protect, fail2ban is still simpler; CrowdSec pays off once you have two or three layers.
Why the switch from fail2ban is worth it
The most important difference is architectural. fail2ban reads logs, decides, and runs an iptables rule in the same process. CrowdSec separates those responsibilities:
- The agent reads logs and emits decisions to the LAPI (a local REST API that listens on
127.0.0.1:8080by default). - Bouncers (the current docs call them remediation components) query the LAPI and enforce the block, whether on the firewall, on a reverse proxy, or on the web server itself.
This separation has practical consequences. You can run a bouncer on the firewall for SSH and another on Traefik for HTTP, and both act on the same decisions: you change detection without touching blocking. And if you contribute your detections to the community, you get back a list of IPs the network has identified as malicious.
The second difference is expressiveness. fail2ban uses regular expressions over log lines; CrowdSec uses scenarios (declarative YAML rules that combine what to detect, at what threshold, over what time window, and how to group matches). The crowdsecurity/ssh-bf scenario, for example, is a bucket with capacity 5 that leaks one event every 10 s.
Installing the agent
On Debian or Ubuntu, add the repository and check which version apt is about to install:
curl -s https://install.crowdsec.net | sudo sh
apt-cache policy crowdsec
sudo apt install crowdsec
The check matters because the distributions stopped at 1.4.6. Debian 13 offers 1.4.6-10+b4 and Ubuntu 24.04 offers 1.4.6-6ubuntu0.24.04.2; with the repository added, the candidate becomes 1.8.1. On Ubuntu with ESM you may need to raise the repository priority, as the installation documentation[2] explains. I ran the full install on Debian; on Ubuntu 24.04 I only checked the candidate.
If you are on 1.7, upgrade: release 1.8.0[3] fixed two denial-of-service vulnerabilities in the HTTP and Kubernetes audit data sources.
During installation, the package registers the machine with the LAPI and the Central API (CAPI) and runs cscli setup, which detects the services present. In the test it found SSH and the system, installed their collections, and wrote setup.sshd.yaml and setup.linux.yaml to /etc/crowdsec/acquis.d/. Confirm it like this:
sudo cscli version
sudo systemctl status crowdsec
version: v1.8.1-debian-pragmatic-arm64-909b5157
Codename: alphaga
BuildDate: 2026-09-03_10:56:41
GoVersion: 1.26.3
The agent and the LAPI run in the same process, listening on localhost, and systemctl status should show active (running).
Collections: work saved from day one
A collection (a package that bundles parsers, the translators for a specific log format, and scenarios ready for a given technology) saves you from writing rules from scratch. The installer already added the Linux and SSH ones. For a typical stack with Traefik, WordPress and Gitea:
sudo cscli collections install crowdsecurity/traefik crowdsecurity/wordpress
sudo cscli collections install LePresidente/gitea
The Gitea collection comes from a community author as LePresidente/gitea. The crowdsecurity/gitea name from the previous version of this guide does not exist: cscli answers can't find 'crowdsecurity/gitea' in collections.
The WordPress one detects login brute force, author enumeration and wp-config.php probing. Within a few days you will have reviewed which scenarios fire on your real traffic.
Telling the agent what to read
This is the step that gets skipped. Acquisition (the configuration that tells the agent what to read and with what label) lives in separate files under /etc/crowdsec/acquis.d/. For Traefik, create traefik.yaml:
source: file
filenames:
- /var/log/traefik/access.log
force_inotify: true
labels:
type: traefik
Without force_inotify, if the log does not exist yet when CrowdSec starts, the agent does not watch the directory (the file data source documentation[4] warns about it). It happened in the test; with the option on, the log created later was read without a restart.
For SSH, the installer already generated this in setup.sshd.yaml:
source: journalctl
journalctl_filter:
- _SYSTEMD_UNIT=ssh.service
labels:
type: syslog
The previous version of this guide had two mistakes here. The source is called journalctl, not journald. And on Debian and Ubuntu the unit is ssh.service: filtering on sshd.service, which is only an alias, returned -- No entries --.
The type label is not arbitrary: parsers filter by it. The Traefik parser accepts any value starting with traefik, but with type: proxy every line fails without an error in the log. cscli explain gives it away:
tail -1 /var/log/traefik/access.log | sudo cscli explain -f- --type proxy
├ s01-parse
| ├ 🔴 crowdsecurity/appsec-logs
| ├ 🔴 crowdsecurity/sshd-logs
| ├ 🔴 crowdsecurity/sshd-success-logs
| └ 🔴 crowdsecurity/traefik-logs
└-------- parser failure 🔴
After every edit, run sudo crowdsec -t && sudo systemctl reload crowdsec. The systemd unit validates the configuration and sends SIGHUP to the process, without restarting it. In the test, sudo cscli metrics show acquisition reported 3.61k lines read and parsed from the Traefik log.
Diagnosis: what to look at in the first days
Three commands worth turning into a habit:
sudo cscli alerts list: triggered alerts (detections).sudo cscli decisions list: active local decisions. With-ait also lists the community blocklist ones.sudo cscli metrics: every metrics table;cscli metrics show acquisitionshows just one.
If the agent has been running more than 24 hours and none of these commands show activity, your acquisition is almost certainly not reading what you think it is.
Bouncers: from detection to effective blocking
CrowdSec detects, but it does not block anything on its own. For Traefik, the CrowdSec documentation[5] points to the maxlerebourg/crowdsec-bouncer-traefik-plugin[6] plugin, which the community maintains. If the reverse proxy is not running yet, start from the Traefik with Docker Compose guide.
The plugin’s current stable release is 1.7.1, from July 31, 2026. It is declared in Traefik’s static configuration:
entryPoints:
web:
address: ":80"
forwardedHeaders:
trustedIPs:
- 172.16.0.0/12
- 192.168.0.0/16
experimental:
plugins:
bouncer:
moduleName: github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin
version: v1.7.1
The forwardedHeaders block is only needed when another proxy or a CDN sits in front, and it takes that proxy’s ranges. Without it, Traefik rewrites X-Forwarded-For and the plugin never sees the client IP: in the test, a banned IP kept getting 200 until I added it.
Generate the bouncer key on the host; cscli prints it only once:
sudo cscli bouncers add traefik-bouncer
Then paste it into the middleware in the dynamic configuration:
http:
middlewares:
crowdsec:
plugin:
bouncer:
enabled: true
crowdsecMode: stream
crowdsecLapiHost: 172.17.0.1:8080
crowdsecLapiKey: your_bouncer_api_key
crowdsecAppsecEnabled: true
crowdsecAppsecHost: 172.17.0.1:7422
forwardedHeadersTrustedIPs:
- 172.16.0.0/12
- 192.168.0.0/16
The package binds the LAPI to 127.0.0.1:8080, which a containerized Traefik cannot reach. Change listen_uri in /etc/crowdsec/config.yaml to the docker0 IP (172.17.0.1 in the test; check yours with ip -4 addr show docker0). Update that URL in local_api_credentials.yaml too, since it pointed at 127.0.0.1. The same goes for api_url in /etc/crowdsec/bouncers/crowdsec-firewall-bouncer.yaml once you install the bouncer below.
With crowdsecMode: stream, the plugin caches banned IPs and refreshes them every 60 s; the default mode, live, queries the LAPI for every IP it has not cached. I measured three rounds of 400 requests on a loaded machine (load average of 46 on 18 cores). The median went from 0.19–0.20 ms without the middleware to 0.23–0.25 ms with the plugin.
For SSH and other non-web services, add the firewall bouncer (there is also an nftables variant):
sudo apt install crowdsec-firewall-bouncer-iptables
Version 0.0.36 detects the local CrowdSec, generates its own key and creates the CROWDSEC_CHAIN chain with ipset sets. In under a minute it loaded the 15,000 IPs of the community blocklist. You now have two bouncers sharing the same decision source.
Turning on the WAF: AppSec and virtual patching
Bouncers block IPs; the AppSec component inspects every HTTP request Traefik forwards to it and answers whether to block it. Install the two collections that the Traefik WAF quickstart[7] asks for:
sudo cscli collections install crowdsecurity/appsec-virtual-patching \
crowdsecurity/appsec-generic-rules
Both are required: the crowdsecurity/appsec-default configuration loads experimental-* rules that only the second one ships. With appsec-virtual-patching alone, CrowdSec stopped at startup with no appsec-rules found for pattern crowdsecurity/experimental-*.
Declare the source in /etc/crowdsec/acquis.d/appsec.yaml, on the same IP as the LAPI:
source: appsec
listen_addr: 172.17.0.1:7422
appsec_configs:
- crowdsecurity/appsec-default
labels:
type: appsec
Apply it with sudo crowdsec -t && sudo systemctl restart crowdsec; I used restart because I had also changed the listen addresses. Then request a .env file, which the vpatch-env-access rule blocks:
curl -s -o /dev/null -w '%{http_code}\n' http://localhost/
curl -s -o /dev/null -w '%{http_code}\n' http://localhost/.env
sudo cscli metrics show appsec
200
403
+---------------------------------------------+
| Appsec '172.17.0.3:7422/' Rules Metrics |
+---------------------------------+-----------+
| Rule ID | Triggered |
+---------------------------------+-----------+
| crowdsecurity/vpatch-env-access | 1 |
+---------------------------------+-----------+
The 172.17.0.3 address was the container acting as the host. An AppSec block only affects that request. The ban comes when one IP triggers two different rules within 60 s: the crowdsecurity/appsec-vpatch scenario overflows and the default profile bans it for 4 hours, as happened after requesting /.env and /.git/config.
That inspection has a cost. On the same loaded machine, the median rose to 1.7 ms per request, with a p95 between 7 and 10 ms.
Release 1.8.0 added bot detection to this component, still alpha according to the bot detection documentation[8]. I did not test it here; the CrowdSec 1.8 bot detection guide covers it.
Captcha remediation: not everything is a ban
The classic WAF mistake is banning everything suspicious and finding out a week later that you were blocking legitimate customers. CrowdSec lets you issue captcha-type decisions instead of ban, and the Traefik bouncer then shows a Cloudflare Turnstile[9] challenge instead of cutting the connection.
Those decisions come from /etc/crowdsec/profiles.yaml. This profile, placed before the ban profile the package ships, asks for a captcha on any scenario with http in its name:
name: captcha_remediation
filters:
- >-
Alert.Remediation == true && Alert.GetScope() == "Ip"
&& Alert.GetScenario() contains "http"
decisions:
- type: captcha
duration: 4h
on_success: break
---
In the middleware, add the provider and its keys:
captchaProvider: turnstile
captchaSiteKey: your_turnstile_site_key
captchaSecretKey: your_turnstile_secret_key
captchaFilePath: /captcha.html
The plugin does not bundle captcha.html: download it from its repository and mount it into the container. I tested with Cloudflare’s test keys[10], which always validate. Twenty-five 404 lines injected into the log ended in an http-probing captcha decision, and an IP under captcha reached the app after submitting the token.
The pattern that works well in production:
- Brute force (someone trying passwords): captcha. The legitimate user solves it and moves on.
- Known exploits (CVEs such as CVE-2021-44228, or access to sensitive paths): direct ban. An exploit bot does not solve captchas.
Turnstile costs nothing: according to Cloudflare’s plans page[11], the Free plan allows up to 20 widgets and unlimited challenges.
The real value of the community blocklist
You no longer need cscli capi register: the package registers the installation with the CAPI, and sudo cscli capi status confirms it with Sharing signals is enabled. That puts you on the community network run by CrowdSec, the company behind the project: you send your detections and receive IPs the network has identified as malicious.
The size depends on what you contribute, according to the community blocklist documentation[12]: 3,000 IPs without contributing, 15,000 when contributing, and no limit on paid plans. The test installation received 15,000 on its first pull (7,691 ssh:bruteforce, 6,408 generic:scan and 901 ssh:exploit), tailored to its scenarios.
Ethical nuance: sharing detections contributes to collective defense, but you are also sending information about your traffic. If your privacy requirements rule that out, sharing: false under api.server.online_client stops it, in exchange for a smaller blocklist.
Monitoring
CrowdSec exposes Prometheus metrics on a local HTTP endpoint, by default at 127.0.0.1:6060/metrics (you can check this in the official metrics documentation[13]). If you already monitor the server with something like the Netdata with Docker guide, this endpoint fits right into that dashboard too. Useful metrics:
cs_bucket_overflowed_total: detections per scenario (the previous version citedcs_bucket_overflow_count, which 1.8.1 does not expose).cs_active_decisions: active decisions by origin and action.cs_appsec_block_total: requests blocked by AppSec.
The official Grafana dashboards repository[14] declares compatibility up to the 1.5 series, so review its panels with 1.8. The most important alert is the absence of metrics for more than five minutes: the agent stopped or acquisition broke.
Whitelists and common mistakes
Before blocking anything seriously, protect your own IPs: your office, the VPN, the CI/CD that deploys, external uptime monitors. Since 1.6.8, the recommended route is cscli allowlists, which accept IPs and CIDR ranges and need no restart:
sudo cscli allowlists create office -d "Our own IPs"
sudo cscli allowlists add office 203.0.113.0/24 -d "office network"
1 decisions deleted by allowlists
When the range was added, CrowdSec deleted the ban an IP in that network already had, and it rejected another one I tried to add afterwards. According to the whitelists guide[15], allowlists also cover AppSec and scenarios. To filter patterns, such as a GET /health, create your own file in /etc/crowdsec/parsers/s02-enrich/ instead of editing whitelists.yaml, which the hub manages.
Another frequent mistake: editing a file and not applying it. CrowdSec does not hot-reload files, so running sudo crowdsec -t && sudo systemctl reload crowdsec after every edit is basic discipline.
When CrowdSec does not pay off
There are scenarios where fail2ban is still simpler and more appropriate:
- A single server with low traffic and no need to share intelligence between nodes.
- No Traefik (no HTTP plugin) and no multiple layers to protect.
CrowdSec starts paying off once you have more than one layer (web, SSH, applications) or when the community blocklist cuts noise in a measurable way. It also pays off when you want to integrate it with Ansible to manage allowlists and configuration centrally across more than one server.
My recommendation
If you are going to try it, do it as a staged rollout:
- Install the agent, review the generated acquisition, add your own services, and leave it a week in detection-only mode with no bouncer.
- Review what fires, tune scenarios if there is noise, add allowlists.
- Only then activate the first bouncer (Traefik) and the AppSec component.
- Wait another week and add captcha remediation for brute force.
- Finally, decide whether to keep sending signals to the CAPI, which the package turns on.
Reaching this level of maturity takes two or three weeks of living with the tool. The investment is worth it for any stack with real exposure to the internet.
Frequently asked questions
How do I know if CrowdSec is actually blocking traffic?
Run sudo cscli decisions list: if rows appear with IPs and a decision type (ban or captcha), local blocks are active. The default view hides the community blocklist; add -a to see it. If the local list stays empty once the agent has been reading normal traffic for hours, check acquisition first with sudo cscli metrics show acquisition before suspecting the bouncer.
Do I need Traefik to use CrowdSec?
No. The agent and the LAPI work the same way without Traefik; what changes is the bouncer. Without a reverse proxy you can use the firewall bouncer with iptables or nftables to block at the network level, and the WAF also works with the Nginx, OpenResty and HAProxy bouncers.
Is CrowdSec’s community blocklist free?
Yes. The package registers your install with the CAPI at no cost. You get the Lite version, up to 3,000 IPs, or the full one, 15,000 IPs, if you contribute your own detections regularly. Paid plans add the Premium version with no size limit, but they are not required for the setup described in this guide.
Sources
- CrowdSec
- installation documentation
- release 1.8.0
- file data source documentation
- CrowdSec documentation
- maxlerebourg/crowdsec-bouncer-traefik-plugin
- Traefik WAF quickstart
- bot detection documentation
- Cloudflare Turnstile
- test keys
- Cloudflare’s plans page
- community blocklist documentation
- official metrics documentation
- official Grafana dashboards repository
- whitelists guide
- CrowdSec 1.8.1 release notes