fail2ban vs CrowdSec on a small VPS: where the rule sets overlap, where they fight each other, and how to pick one without re-banning everything

I’ve been running fail2ban on this Oracle box since the day I provisioned it. Six months ago I added CrowdSec because I wanted the community blocklist for SSH brute-force IPs. For three months they coexisted and I assumed it was fine. Then I noticed something weird: SSH brute-force attempts were getting banned twice — once by fail2ban’s local jail, once by the CrowdSec iptables bouncer — and my /var/log/auth.log was twice as noisy as it needed to be. The two are doing the same job, with overlapping rules, and they don’t know about each other.

Here’s the honest comparison after running both side-by-side, and how I picked one without losing my baseline protection.

What each one is actually good at

fail2ban is a log-watcher. It tails files like /var/log/auth.log, matches regexes, counts hits per IP, and inserts iptables rules when an IP crosses a threshold. It’s been the default since 2004 and the model is stupidly simple: regex + threshold + ban. The pros: tiny footprint, every regex is in a file you can read, no daemon talking to a third party. The cons: you maintain the regexes yourself, the ban list is local-only (every server learns the hard way), and the matching is per-line — context across requests is hard.

CrowdSec is a behaviour engine plus a federated blocklist. The agent parses logs through “scenarios” (more sophisticated than regexes — they can correlate across multiple lines), decides if a behaviour is hostile, and reports the offending IP to a central API in exchange for access to everyone else’s reports. The actual blocking is delegated to “bouncers” — separate processes that apply decisions to iptables, nginx, traefik, etc. The pros: federated intel (you’re protected from IPs that hit other people’s servers an hour ago), better correlation, modular architecture. The cons: more moving parts, requires a free account, and the heuristics are slower to debug when they ban something they shouldn’t.

Where the rule sets overlap (a lot)

If you run both with default configs, you get duplicate coverage on:

  • SSH brute force (sshd jail in fail2ban / crowdsecurity/ssh-bf in CrowdSec)
  • WordPress xmlrpc / wp-login probes (if you’ve enabled the WP collection on either)
  • Nginx / Apache / LSWS 4xx and 5xx flooding
  • Postfix / dovecot auth failures

Both will see the same log line, both will (independently) add an iptables rule, and both will (independently) decide when to expire the ban. You don’t get double the protection — you get double the rule churn and double the chance one of them un-bans an IP the other still considers hostile.

Where they actually fight each other

Two real conflicts I hit:

  • iptables chain order. CrowdSec’s bouncer creates a CROWDSEC-BLOCKLISTS chain referenced from INPUT. fail2ban creates one f2b-* chain per jail, also referenced from INPUT. The order is determined by who started first. If fail2ban inserts at position 1 and CrowdSec inserts at position 1 later, fail2ban’s rules now run after CrowdSec’s. Sometimes that’s fine; sometimes a CrowdSec block expires while a fail2ban block is still active and you get an asymmetric state.
  • Whitelist mismatches. I had my office IP whitelisted in fail2ban’s jail.local. I forgot to add it to CrowdSec’s whitelist scenario. CrowdSec banned my office, fail2ban let me through, the result was an iptables DROP rule that fail2ban knew nothing about, and I sat there typing fail2ban-client status sshd looking very confused while my SSH connection hung.

How I picked CrowdSec, and how I got out cleanly

The federated blocklist alone won the argument. Within a week of enabling the community blocklist, I was blocking ~3,500 known-hostile IPs that hadn’t yet hit my server but had been reported by other CrowdSec instances in the last 24 hours. fail2ban can’t do that without you running a custom bouncer scraping AbuseIPDB or similar — which is, at that point, the same architecture as CrowdSec but worse.

Migration was three steps:

# 1. Verify CrowdSec is matching everything fail2ban was matching
sudo cscli scenarios list | grep -E 'ssh|http|wp-login'
sudo cscli decisions list

# 2. Snapshot fail2ban's existing bans before draining
sudo iptables-save | grep f2b- > /root/f2b-snapshot.txt
sudo systemctl stop fail2ban && sudo systemctl disable fail2ban

# 3. Confirm iptables is now driven only by CrowdSec
sudo iptables -L -n -v | head

I kept the snapshot for two weeks in case I had to roll back; nothing went wrong, so I trashed it.

If you want to stay on fail2ban

That’s a defensible choice — especially if your server is small, your threat model is “casual scanners,” and you don’t want a daemon that talks to a third-party API. fail2ban gets you 80% of the value at 30% of the operational complexity. The thing you’ll miss is community intel; the thing you won’t miss is having to debug two banning systems.

Whichever you pick: only run one. The cost of running both is much higher than the cost of either alone, and the protection benefit of stacking them is essentially zero.

Cover photo: Tima Miroshnichenko on Pexels.

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.