fail2ban has guarded my SSH and nginx for years and I still run it in plenty of places. But CrowdSec fixes two things that always nagged me: fail2ban only knows what your box has seen, and it tangles detection and blocking into one process. CrowdSec splits those apart and adds a community angle — if a thousand other servers already flagged an IP for hammering WordPress logins, you can block it before it ever reaches you. Here's how I set it up and where I still keep fail2ban.
TL;DR:
# install (Debian/Ubuntu repo)
curl -s https://install.crowdsec.net | sudo sh
apt install -y crowdsec
# add the thing that actually blocks — a bouncer
apt install -y crowdsec-firewall-bouncer-iptables
# see decisions and alerts
cscli decisions list
cscli metrics
Details below. If you're coming from fail2ban, keep /fail2ban-setup-ssh-nginx/ open for comparison.
The mental model: agent vs. bouncer
This split is the whole point, and it confuses everyone at first:
- The agent reads your logs, matches them against scenarios (SSH brute-force, nginx path scanning, etc.), and produces decisions — "IP x.x.x.x should be banned for 4h". It decides. It does not block.
- A bouncer is a separate component that enforces decisions. The firewall bouncer drops banned IPs at iptables/nftables; there are bouncers for nginx, Traefik, Caddy, and more.
fail2ban is both in one process. CrowdSec separates them so the same detection can drive blocking at the firewall, the reverse proxy, or the app layer — and so detection can't take your traffic path down with it. Install the agent and you have detection with no enforcement until you add a bouncer. That trips up everyone's first setup: "it sees the attacks but doesn't block them" — because there's no bouncer yet.
Install and confirm it's watching
curl -s https://install.crowdsec.net | sudo sh
apt install -y crowdsec
systemctl status crowdsec
On install it auto-detects services (SSH, nginx, …) and enables matching collections. Check what it's parsing:
cscli collections list # installed detection bundles
cscli parsers list
cscli metrics # lines read per source — proves it's seeing logs
If cscli metrics shows lines being read from /var/log/auth.log, the agent is alive and watching. Grab more scenarios from the hub as needed:
cscli collections install crowdsecurity/nginx
cscli collections install crowdsecurity/sshd
systemctl reload crowdsec
Add the bouncer that does the blocking
Nothing gets banned until a bouncer enforces decisions. For a plain server, the firewall bouncer is what you want:
apt install -y crowdsec-firewall-bouncer-iptables
# (nftables users: crowdsec-firewall-bouncer-nftables)
systemctl status crowdsec-firewall-bouncer
It registers itself with the agent automatically and starts dropping banned IPs at the firewall. Confirm the wiring:
cscli bouncers list # your bouncer should be here, last-pull recent
cscli decisions list # active bans right now
Test the flow end to end by banning yourself a throwaway IP:
cscli decisions add --ip 203.0.113.55 --duration 4h --reason "manual test"
cscli decisions list # it shows up
iptables -L -n | grep 203.0.113.55 # the bouncer enforced it
cscli decisions delete --ip 203.0.113.55
The community blocklist
Register with the (free) central API and you pull a curated list of IPs that the CrowdSec network has flagged for real, ongoing malicious behaviour — pre-emptive blocking of the internet's worst actors:
cscli capi register
systemctl reload crowdsec
cscli decisions list -o raw | wc -l # community decisions now included
You share signals (an IP + which scenario it tripped — not your logs, not your data) and in return you consume the aggregate. This is the bit fail2ban structurally can't do: your firewall benefits from a thousand other boxes' pain. If you'd rather not participate in sharing, you can run fully local and just lose the community feed — the local detection still works.
Gotchas
- Detection without enforcement. The number-one "it's not working": agent installed, no bouncer. It's seeing everything and blocking nothing. Add a bouncer.
- Reading the log, not tailing the right one. If
cscli metricsshows zero lines for a source, CrowdSec isn't pointed at that log. Check the acquisition config in/etc/crowdsec/acquis.yaml— wrong path = silent no-op, exactly like a bad fail2banlogpath. - Running it alongside fail2ban on the same service. Both will try to ban and you'll double-block and confuse yourself debugging. Pick one enforcer per service. I run CrowdSec on internet-facing web boxes and still use fail2ban on simple single-purpose SSH boxes — but never both on the same jail.
- iptables vs nftables bouncer. Install the one matching your firewall backend. The wrong one installs cleanly and enforces nothing. Modern Debian/Ubuntu default to nftables.
- Decisions expire. Bans have a duration and lift themselves — good for transient scanners, but a persistent attacker comes back. Lengthen durations or lean on the community list for the repeat offenders.
So, replace fail2ban?
Not everywhere. On a small box that only needs SSH brute-force protection, fail2ban is simpler and I leave it. Where CrowdSec earns its keep is anything internet-facing and multi-service: the agent/bouncer split lets me block at the reverse proxy and the firewall from one detection, and the community list stops known-bad traffic before it costs me anything. Layer it with the basics that don't change — a tight firewall in /ufw-firewall-rules-ubuntu/ and a hardened SSH config in /ssh-hardening-ubuntu-checklist/. Detection is the last layer, not the first.