CrowdSec vs Fail2ban: Community Threat Intel at the Edge

Two ways to ban the bad actors hammering your front door — one local, one networked

Contents

Anything you expose to the internet gets probed within minutes. Stand up a fresh box, open port 22 or 443, and the logs fill with login attempts for admin, root, oracle and a hundred other stock usernames, plus a steady drizzle of requests for /wp-login.php, /.env, and /phpMyAdmin. None of it is targeted at you. It is broad, automated, and relentless, and the machines running it have already tried the same tricks on a million other addresses today.

Two tools dominate the homelab answer to that noise: Fail2ban, the veteran, and CrowdSec, the newer arrival with a networked twist. Both watch your logs, spot patterns that look like an attack, and drop a firewall rule to lock the offender out. The difference is what happens after the ban, and that difference is the whole reason to have this conversation. I have run both in earnest, and I want to walk through how each one actually works, where they diverge, and which one earns a place on your boxes.

What Fail2ban actually does

Advertisement

Fail2ban has been the default reflex for over a decade, and the reason is that its model is simple enough to hold in your head. It tails a log file, matches lines against a regular expression, and counts how many times a given IP address trips that expression within a window. Cross the threshold and Fail2ban invokes a firewall action — usually an iptables or nftables rule — that blocks the address for a set duration. That grouping of a filter, a log source, and an action is called a jail.

A minimal SSH jail looks like this in /etc/fail2ban/jail.local:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
[sshd]
enabled  = true
port     = ssh
backend  = systemd
filter   = sshd
maxretry = 4
findtime = 10m
bantime  = 1h
# escalate repeat offenders
bantime.increment = true
bantime.factor    = 2
bantime.maxtime   = 1w

That reads as: watch the systemd journal for sshd, and if one address fails authentication four times inside ten minutes, ban it for an hour. The bantime.increment block is the part people skip and shouldn’t — it doubles the ban every time an address comes back, so a persistent scanner climbs from an hour to a day to a week without you lifting a finger.

The filters themselves are just regexes living in /etc/fail2ban/filter.d/. Fail2ban ships dozens covering SSH, Apache, Nginx, Postfix, Dovecot, and more, and writing your own for a bespoke app is a twenty-minute job once you understand the failregex syntax. That flexibility is Fail2ban’s real strength. If a service writes a log line when something bad happens, you can teach Fail2ban to ban on it.

The limitation is baked into the same design. Fail2ban only knows what your logs have seen. An attacker gets four free guesses against your SSH daemon before the door shuts, every single time, from every fresh address in their pool. Botnets have thousands of addresses. Each one arrives clean, spends its four attempts, gets banned, and its neighbour steps up to take another four. Fail2ban is a memory of what has attacked this host, and it starts every relationship from zero.

What CrowdSec changes

CrowdSec keeps the same core loop — parse logs, detect patterns, ban offenders — and splits it into pieces that talk over an API. The agent reads your logs and runs them through scenarios (its equivalent of jails). When a scenario fires, the agent doesn’t touch the firewall itself. It records a decision in a Local API (the LAPI). Separately, one or more bouncers poll that API and enforce whatever decisions they find, at whatever layer they live in — a firewall bouncer writing nftables sets, an Nginx bouncer returning 403s, a Cloudflare bouncer pushing rules to the edge.

That separation feels like ceremony until you notice what it buys you. Because detection and enforcement are decoupled, one CrowdSec agent can feed decisions to many enforcement points, and many agents can report into one LAPI. A single ban decision made by the box reading your reverse-proxy logs can be enforced simultaneously at your firewall, your proxy, and your CDN.

The headline feature sits on top of that plumbing. CrowdSec agents share their signals — an anonymised report of “this IP tripped a brute-force scenario” — with a central service, which aggregates millions of these reports and curates a community blocklist of addresses that are demonstrably attacking people right now. Your agent pulls that list down and your bouncers enforce it. The upshot is that an address which spent last night hammering someone else’s mail server in another country is already blocked at your edge before it ever sends you a packet. You inherit the herd’s immunity.

Installation on a Debian-family box pulls the agent and the LAPI together:

1
2
3
4
5
6
7
8
9
curl -s https://install.crowdsec.net | sudo sh
sudo apt install crowdsec

# tell the agent which logs to read
sudo cscli collections install crowdsecurity/sshd crowdsecurity/nginx
sudo systemctl reload crowdsec

# add a firewall bouncer to actually enforce bans
sudo apt install crowdsec-firewall-bouncer-nftables

Collections bundle the parsers and scenarios for a given service, so installing crowdsecurity/sshd teaches the agent to understand SSH logs and gives it the brute-force scenarios that go with them. Log sources live in /etc/crowdsec/acquis.yaml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
filenames:
  - /var/log/auth.log
labels:
  type: syslog
---
source: journalctl
journalctl_filter:
  - "_SYSTEMD_UNIT=ssh.service"
labels:
  type: syslog

Everyday operation happens through cscli, which is genuinely pleasant to use:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$ sudo cscli decisions list
+--------+----------+-------------------+---------+--------+---------+
|   ID   |  SOURCE  |     REASON        | ACTION  | COUNTRY|   AS    |
+--------+----------+-------------------+---------+--------+---------+
| 148213 | crowdsec | ssh-bf            | ban     |  RU    | AS12345 |
| 148219 | lists    | firehol_cruzit    | ban     |  CN    | AS67890 |
+--------+----------+-------------------+---------+--------+---------+

$ sudo cscli metrics        # parse/scenario hit counts, bouncer activity
$ sudo cscli alerts list    # what tripped, when, and from where

Notice the SOURCE column. Decisions marked crowdsec came from your own logs; decisions marked lists arrived from the community blocklist. The second kind is the whole pitch, and on an exposed box it typically outnumbers the first by a wide margin.

The comparison that matters

Advertisement

Set side by side, the tools split along a few clear lines.

Detection reach. Fail2ban sees your logs. CrowdSec sees your logs plus a curated feed of what is attacking the wider community. On a box behind CGNAT with almost no exposure the difference is small; on a public-facing reverse proxy it is the difference between reacting to attacks and pre-empting them.

Enforcement model. Fail2ban detects and blocks in one process on one host. CrowdSec’s agent, LAPI and bouncer split those roles, which is more moving parts to grasp but pays off the moment you want central decisions enforced across several machines or pushed out to a CDN.

False positives. Both can lock you out of your own service if you fat-finger a password from an unfamiliar address — always allowlist your management range. CrowdSec’s community list carries a second risk: you are trusting someone else’s judgement about who is hostile. The curation is good and the list leans conservative, but you are importing decisions you didn’t make. cscli decisions add --ip x.x.x.x --type whitelist and a whitelist parser handle the addresses you never want touched.

Resource cost. Fail2ban is a lean Python daemon that sips memory. CrowdSec’s Go agent uses more, and you are running a bouncer process alongside it. On anything past a Raspberry Pi the difference is noise.

Ecosystem. Fail2ban’s regex filters will match literally any log line you can describe, which makes it unbeatable for weird bespoke services. CrowdSec’s collections cover the common services well and its bouncer catalogue — firewall, Nginx, Traefik, Cloudflare, and more — extends enforcement to layers Fail2ban never reaches.

Running them together

You do not have to choose, and for a while I didn’t. Fail2ban guarded a couple of oddball services with hand-rolled filters while CrowdSec handled SSH and the reverse proxy and pulled the community blocklist. The one rule is to keep them off the same log source and the same firewall chain, because two tools racing to ban the same address is a debugging headache with no upside. Give each one a clear beat and they coexist happily.

If you are wiring this into a broader security posture, it slots in alongside the other edge controls. A CrowdSec or Fail2ban ban is a blunt outer layer; it pairs naturally with hardening SSH properly so that the handful of attempts that get through before a ban lands hit key-only authentication and go nowhere. And when you are deciding how much of this you actually need, it is worth running the exercise in a homelab threat model first, because the answer depends entirely on what you have exposed and to whom.

Troubleshooting the things that will bite you

Bans that don’t ban. The commonest CrowdSec confusion is seeing decisions in cscli decisions list while offenders still get through. Decisions are intentions; a bouncer enforces them. No bouncer, no block. Check cscli bouncers list shows a registered, recently-seen bouncer, and confirm the firewall bouncer’s nftables set actually exists with nft list ruleset | grep crowdsec.

Fail2ban ignoring the journal. If backend = systemd yields nothing, the usual cause is a missing python3-systemd binding, or sshd logging to a file the filter isn’t watching. fail2ban-client status sshd shows the file list and the current failure count; if the count never moves, the filter’s regex isn’t matching your log format. fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf tests a filter against real lines and tells you exactly how many it caught.

Locking yourself out. Both tools will happily ban you. For Fail2ban, ignoreip = 127.0.0.1/8 192.168.1.0/24 in the jail keeps your LAN safe. For CrowdSec, a whitelist parser under /etc/crowdsec/parsers/s02-enrich/ covering your management ranges does the same. Set this up before you leave the house, not after you have locked yourself out of a remote box.

The IPv6 blind spot. If your services answer on IPv6 and your firewall action only touches iptables, you are banning half an attacker’s addresses. Make sure the action covers ip6tables/nftables inet, or an attacker simply switches to their v6 address and walks straight in.

Behind a reverse proxy. If everything sits behind a proxy, the source IP in your app logs is the proxy, and you will ban yourself the instant a scenario fires. Parse the real client from X-Forwarded-For, and make sure the proxy is the only thing allowed to set that header, or an attacker spoofs it and gets someone innocent banned.

The verdict

If you run a single box with light exposure and you want something that has worked reliably for a decade, Fail2ban is a completely respectable answer and I would not talk you out of it. Its regex model is more flexible than anything CrowdSec offers, and there is real value in a tool simple enough to fully understand.

For anything meaningfully exposed to the internet, I now reach for CrowdSec first. The community blocklist genuinely changes the maths: a large share of the addresses it blocks never get to make their first request to me, which means the noise in my logs drops and the attacks I do see are the more interesting minority. The decoupled agent-and-bouncer design also scales the way a growing homelab does, letting one set of decisions guard several machines. The cost is more concepts to learn and slightly more memory, both of which are easy to pay.

My actual setup is CrowdSec on the exposed edge and Fail2ban kept in reserve for the strange corners. Whichever you pick, treat it as one layer among several. A ban at the edge buys you time and quiet; it is the audit trail and the hardened services behind it that decide what happens if something does get through. Pick one this weekend, allowlist your own address first, and watch how much of the internet’s background hum you can make disappear.

Advertisement
Advertisement
Smarc
Written by Smarc

Founder and editor of vo.rs. A lifelong tinkerer who self-hosts far more than is sensible, hardens Linux boxes for fun, and prods the latest AI tools to see what they can really do. The how-to guides here are the notes Smarc wishes had existed the first time round.