Fail2ban vs CrowdSec: How They Block Malicious Traffic and When to Use Which
The first thing that happens after you assign a public IP to a fresh server is usually the same thing that happened to mine: a botnet starts hammering SSH within minutes. I have watched auth logs fill with failed passwords for user names that do not exist, from IPs that change every few hours.
For two decades the standard answer has been Fail2ban: watch the logs, count the failures, ban the source. CrowdSec is the challenger that turns that model inside out. Instead of one host learning alone, every host shares what it saw with everyone else.
This post is the sysadmin's-eye comparison. I cover how each tool actually detects and blocks, with real configuration and commands, the architectural difference that matters (local log analysis versus a community of shared signals), honest pros and cons, and when you should run Fail2ban, CrowdSec, or both on the same box.
The short version
- Fail2ban is a per-host log watcher. It scans log files, matches failures against regex filters, and bans IPs after a configurable number of failures inside a time window. Zero network dependencies, a tiny footprint, and it has been in every distro repo for two decades.
- CrowdSec is a detection engine plus a community. A Go-based engine parses logs into events, matches them against scenarios, and writes decisions that separate bouncer programs enforce. Its real differentiator: anonymized signals are shared with a central console, and every contributor receives a curated blocklist of IPs seen attacking other people.
- Detection and remediation are split in CrowdSec, and fused in Fail2ban. Fail2ban ties a filter to an action inside one jail. CrowdSec detects in one process and enforces in another (firewall bouncer, nginx bouncer, Cloudflare bouncer), which is what lets one detection feed many enforcement points.
- Fail2ban reacts to your logs; CrowdSec can block before the attacker reaches you. The community blocklist means an IP that attacked someone else's SSH yesterday can already be blocked on your firewall today, which Fail2ban structurally cannot do.
- Both are a layer, not a silver bullet. Disabling password auth over SSH is a bigger win than any ban tool, and both tools occasionally false-positive on legitimate traffic. Whitelist yourself first.
- Fail2ban
0.11.x (0.11.2, Nov 2023) - CrowdSec Security Engine
1.6.x current, 1.7 next - crowdsec-firewall-bouncer-nftables
current from CrowdSec apt repo - Community Blocklist
community tier, ~15k IPs
Checked 2026-08-13 against the CrowdSec docs (Community Blocklist page, install guide), CrowdSec's own Fail2ban comparison post, the jail.conf(5) man page, and the Linode Fail2ban tutorial. Versions and paths reflect those sources; re-check before you deploy.
What each tool actually is
Fail2ban: the log-watching classic
Fail2ban is a Python daemon that has shipped in every major distro since the mid-2000s. The mental model is simple. Pick a log source (sshd's log, an nginx access log, a mail log), define a regex that picks out the failure, count how many times each IP matches within a window, and when the count crosses a threshold, run an action that blocks the IP for a duration. When the time expires, the action is reversed.
The pieces of that pipeline are named explicitly in its config:
- Backend. Where the log comes from. Fail2ban can tail a file, read the systemd journal, or poll a syslog socket. On modern distros the systemd backend is the default and needs no path configuration.
- Filter.
filter.d/sshd.confcontains the failregex lines. This is where most of the maintenance lives, because log formats drift between distros and versions. - Action.
action.d/*.confcontains the ban and unban commands, usually an nftables or iptables rule insertion. - Jail. A named combination of a filter and an action with its own thresholds, defined in
jail.confand overridden injail.local.
The practical configuration lives in /etc/fail2ban/jail.local:
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
ignoreip = 127.0.0.1/8 ::1 192.168.1.0/24
[sshd]
enabled = trueRead those four settings as: ban an IP for one hour if it produced five matching failures within ten minutes, and never ban anything on my own network. The shipped defaults (bantime 600 seconds, findtime 600 seconds, maxretry 3) are aggressive for a box with a real login surface; raising maxretry and adding your management addresses to ignoreip is the first thing most people do.
$ sudo systemctl enable --now fail2ban
$ sudo fail2ban-client status
Status
|- Number of jail: 1
`- Jail list: sshd
$ sudo fail2ban-client status sshd
Status for the jail: sshd
|- Filter
| |- Currently failed: 1
| `- Total failed: 18
`- Actions
|- Currently banned: 1
| `- IP list: 203.0.113.9
`- Total banned: 12
# lift a ban that turned out to be a mistake
$ sudo fail2ban-client set sshd unbanip 203.0.113.9
# apply jail.local changes without restarting the service
$ sudo fail2ban-client reloadTwo honest limits before we move on. First, Fail2ban is blind to anything it has not seen itself: a distributed attack from ten thousand IPs, each trying once, never trips maxretry for any single source, and a brand-new botnet IP is indistinguishable from a legitimate user until it fails. Second, every host learns alone. Ban an IP on server A and server B still takes the full beating until its own counter trips. Both of these are exactly what CrowdSec is built to fix.
How CrowdSec detects and blocks
CrowdSec, first released in 2019, describes itself as Fail2ban's big brother, and the family resemblance is real: it still watches logs and still bans IPs. What changed is the architecture and the data sharing. The engine is written in Go, and detection is deliberately decoupled from enforcement:
- Acquisition. The engine ingests logs from files or the systemd journal (YAML configuration under
/etc/crowdsec/acquis.d). - Parsers. Normalize raw lines into structured events. The
crowdsecurity/sshd-parserturns an sshd log line into an event with a source IP and a user name. - Scenarios. Describe attack patterns over those events.
crowdsecurity/ssh-bftriggers after N failed attempts in a window,crowdsecurity/http-probingdetects scanning against nginx, and so on. - Decisions. When a scenario fires, the engine writes a decision (usually a ban with a duration) into its local database and exposes it through the Local API (LAPI) on
127.0.0.1:8080. - Bouncers. Separate programs poll LAPI and enforce the decisions. The firewall bouncer writes nftables or iptables sets; the nginx and Apache bouncers run Lua inside the web server and can answer with a 403 or a captcha at the application layer; there are Cloudflare, Caddy, and other bouncers.
The scenario system is the direct descendant of Fail2ban's jail, but it works on normalized events rather than raw regexes, which means one scenario can reason about behavior across services instead of matching one log line shape.
The second big piece is the community. The engine can send anonymized signals to the CrowdSec Console (the central API), and in exchange contributors receive the Community Blocklist: a curated set of IP addresses observed attacking other participants. The CrowdSec docs describe it as a fair trade model; contribute signals regularly and you stay subscribed. The community tier sits around 15 thousand IPs at the time of writing, tailored to what your engine actually detects, and your firewall bouncer blocks those IPs even if you have never seen them yourself.
A minimal install looks like this:
# add the CrowdSec apt repo and install the engine
$ curl -s https://packagecloud.io/install/repositories/crowdsec/crowdsec/script.deb.sh | sudo bash
$ sudo apt-get install -y crowdsec
$ cscli version
# install a detection collection for SSH brute force
$ sudo cscli collections install crowdsecurity/sshd
$ sudo systemctl restart crowdsec
# register a firewall bouncer and install it (nftables)
$ sudo cscli bouncers add local-firewall
$ sudo apt-get install -y crowdsec-firewall-bouncer-nftables
# what did the engine decide, and why?
$ sudo cscli alerts list
$ sudo cscli decisions list
$ sudo cscli decisions delete --ip 203.0.113.9Collections are the Fail2ban equivalent of a jail plus its filter, packaged: crowdsecurity/sshd brings the parser, the scenario, and the postoverflow in one command. I keep collections updated the same way I track every dependency, using the release-tracking workflow I wrote up earlier. You can also run CrowdSec fully offline: local detection and local bouncers work without the console; you just lose the blocklist and the shared signals.
The key differences at a glance
| Feature | Fail2ban | CrowdSec |
|---|---|---|
| Architecture | Python daemon; filter and action fused per jail | Go engine + LAPI + separate bouncers |
| Detection | Regex filters on local logs | Parsers + scenarios over normalized events |
| Shared intelligence1 | None; each host learns alone | Community signals + curated blocklist |
| Enforcement | iptables/nftables action per jail | Firewall, nginx, Apache, Cloudflare, Caddy bouncers |
| Config style | jail.local + filter.d regex | YAML acquisition/parsers/scenarios + cscli collections |
| Resource use | Very light (a few MB RAM) | Heavier: engine, local DB, plus a bouncer |
| Network dependency | None; fully offline | Console optional; blocklist needs it |
| Best at | Known local patterns, quick custom jails | Distributed attacks, fleets, app-layer blocking |
Architecture
- Fail2ban
- Python daemon; filter and action fused per jail
- CrowdSec
- Go engine + LAPI + separate bouncers
Detection
- Fail2ban
- Regex filters on local logs
- CrowdSec
- Parsers + scenarios over normalized events
Shared intelligence1
- Fail2ban
- None; each host learns alone
- CrowdSec
- Community signals + curated blocklist
Enforcement
- Fail2ban
- iptables/nftables action per jail
- CrowdSec
- Firewall, nginx, Apache, Cloudflare, Caddy bouncers
Config style
- Fail2ban
- jail.local + filter.d regex
- CrowdSec
- YAML acquisition/parsers/scenarios + cscli collections
Resource use
- Fail2ban
- Very light (a few MB RAM)
- CrowdSec
- Heavier: engine, local DB, plus a bouncer
Network dependency
- Fail2ban
- None; fully offline
- CrowdSec
- Console optional; blocklist needs it
Best at
- Fail2ban
- Known local patterns, quick custom jails
- CrowdSec
- Distributed attacks, fleets, app-layer blocking
- community tier ~15k IPs
Real-world use cases
- A single VPS with SSH exposed: Fail2ban is the natural fit. One package, one jail, done, and it keeps working if the host loses network access.
- An air-gapped or policy-restricted environment: Fail2ban. CrowdSec's value is the community, and if the host cannot phone home, most of that value disappears.
- A fleet of servers behind the same threat model: CrowdSec. One detection feeds many bouncers, and every box starts from the blocklist instead of from zero.
- A reverse proxy or web app under constant HTTP abuse: CrowdSec's nginx and Apache bouncers can answer with a 403 or a captcha at the application layer, which is far more precise than a firewall ban and does not punish shared-hosting IPs as blindly.
- A mail server with an existing filter setup: Fail2ban's postfix and dovecot jails are battle-tested and trivially customizable, which matters when a regex tweak is the difference between blocking a spammer and dropping a real customer's retry.
- A homelab behind a VPN already: honestly, often neither. If the only path to your services is an encrypted tunnel (the NetBird and Pangolin comparison covers that stack), the brute-force surface is tiny, and a ban tool is a moving part you may not need. The argument I make in my server-hardening playbook applies here too: disabling password auth and changing default ports is the bigger win.
Pros and cons
Fail2ban
Pros
- In every distro repository, documented everywhere, and stable for two decades.
- Tiny resource footprint and zero network dependencies.
- Custom jails are a regex and an action away; you can protect nearly anything that writes a log.
- The systemd journal backend removed the fiddly log-path configuration.
Cons
- Per-host blindness: every machine must learn an attack on its own.
- Regex maintenance: filters break when log formats drift, and regexes cannot express behavior across services.
- Reacts only after the fact, and slow, distributed attacks slip under the maxretry radar.
- Enforcement is firewall-shaped; there is no application-layer response like a captcha.
CrowdSec
Pros
- Community intelligence: contributors share signals, and everyone gets the curated blocklist, which blocks known-bad IPs before they ever touch your services.
- Split detection and remediation: one engine can feed a firewall bouncer here and an nginx bouncer there, and you can detect on one host and enforce on another.
- Application-layer response: the web bouncers can serve 403s or captchas instead of raw firewall bans.
- Structured events and collections make new detections mostly configuration, not regex archaeology.
Cons
- Heavier: a Go engine, a local database, and at least one bouncer, plus the LAPI surface to secure (bind it to localhost).
- The community features assume network access and, for the full experience, an account on the console; the premium blocklist tiers are a commercial product.
- More moving parts to patch: engine, collections, and bouncers each update on their own cadence.
- The ecosystem is younger and smaller; if you need a parser for an obscure daemon, you may be writing it yourself.
When to choose Fail2ban, CrowdSec, or both
Choose Fail2ban when: you run a small number of boxes, you want zero external dependencies, you value two decades of documented behavior, or you need a quick custom jail for a service no one else runs.
Choose CrowdSec when: you run a fleet, you face HTTP-layer abuse behind a reverse proxy, or the shared blocklist genuinely reduces the noise you see, which it measurably does for public-facing web servers.
Run both when: you want the community blocklist enforced at the firewall by CrowdSec while Fail2ban keeps the per-service jails you have already tuned. This is a common and reasonable homelab setup.
My own rule of thumb: Fail2ban on the one-off VPS, CrowdSec when the box sits behind a reverse proxy or belongs to a fleet, and both when I want the blocklist plus a jail I have already tuned. Whatever you pick, whitelist yourself, keep the logs, and remember that the ban is the least interesting part of the defense. The SSH brute force you see in the auth log is noise; what matters is that nothing you run accepts a password over the network.
Official sources
- Fail2ban documentation: https://www.fail2ban.org/ and jail.conf(5): https://manpages.ubuntu.com/manpages/focal/man5/jail.conf.5.html
- CrowdSec docs, Community Blocklist: https://docs.crowdsec.net/docs/centralapi/communityblocklist/
- CrowdSec: not your typical Fail2Ban clone: https://www.crowdsec.net/blog/crowdsec-not-your-typical-fail2ban-clone
- CrowdSec documentation root (install, collections, bouncers): https://docs.crowdsec.net/
- Fail2ban usage reference (Arch wiki): https://wiki.archlinux.org/title/Fail2ban
- Linode tutorial on Fail2ban: https://www.akamai.com/cloud/guides/using-fail2ban-to-secure-your-server-a-tutorial
Are you on Fail2ban, CrowdSec, or both? What made you switch, and did the community blocklist live up to the hype? Drop your setup in the comments.
Until next time, keep your systems thoughtful.

No comments yet