We used to run Fail2ban on every server we deployed. It did the job well enough for years: watch a log file, count failed attempts, add an iptables rule when a threshold was crossed. Simple, reliable, and almost universal in the Linux sysadmin world. The problem is that Fail2ban operates in isolation. Every server learns from its own logs and its own logs only. An IP that spent three days brute-forcing SSH on someone else’s server arrives at yours completely clean, with no history and no penalty, and gets its full quota of attempts before anything happens. That model made sense when the internet was smaller and attacks were less coordinated. It doesn’t really hold up anymore.
CrowdSec takes a fundamentally different approach. It still watches your logs and bans IPs locally, but it also participates in a global threat intelligence network. When CrowdSec on your server identifies and bans a malicious IP, that decision gets shared with every other CrowdSec installation in the community. In return, your server receives a continuously updated blocklist of IPs that the community has already identified as malicious, before they ever make a single request to your server. That community blocklist, called CAPI, is what makes CrowdSec genuinely different rather than just a modernised Fail2ban. We switched every CloudSonic server to CrowdSec and haven’t looked back.
Ubuntu 24.04, the nftables firewall bouncer, the collections we install on every server, and a walkthrough of real production metrics from one of our servers. Commands should be run as root or with sudo.Why CrowdSec beats Fail2ban for modern server security
The core architectural difference comes down to collective intelligence versus individual learning. Fail2ban watches your logs and reacts to what it sees on your specific server. If an attacker is careful and stays just under your threshold, it never fires. If an IP has been hammering every WordPress installation on the internet for six months but hasn’t hit your server yet, Fail2ban has no idea. CrowdSec knows, because thousands of other servers have already reported that IP to the community blocklist.
Beyond the shared intelligence, CrowdSec has a cleaner architecture for modern servers. Fail2ban uses iptables by default, which works but is being superseded by nftables on current Ubuntu releases. CrowdSec separates the detection engine from the enforcement layer through a component called a bouncer. The engine reads logs and makes decisions. The bouncer enforces them, and there are bouncers for nftables, Nginx, Cloudflare, and a growing list of other targets. This separation means you can change how bans are enforced without touching the detection configuration, which matters when you’re managing a fleet of servers with different roles.
CrowdSec also has proper scenario-based detection rather than simple threshold counting. A scenario like crowdsecurity/ssh-time-based-bf understands that attackers deliberately slow down their attempts to evade rate-based detection. It tracks attempt patterns over time rather than just counting failures in a fixed window. Our production metrics show three separate SSH brute force scenarios firing simultaneously on a single server because attackers are running all three timing strategies in parallel, hoping one gets through. Fail2ban with a simple retry threshold catches the aggressive ones and misses the patient ones entirely.
How to install CrowdSec on Ubuntu 24.04
CrowdSec provides an official installation script that handles repository setup across all current Ubuntu releases. This is the approach we use because it works correctly on 22.04, 24.04, and 26.04 without needing to manually add package sources.
curl -s https://install.crowdsec.net | bash
apt-get install -y crowdsec
The script adds the CrowdSec packagecloud repository and sets up the GPG key. The subsequent apt install pulls the actual CrowdSec security engine. Once installed, CrowdSec starts automatically and begins reading your system logs immediately. It won’t block anything yet because that requires a bouncer, but it’s already detecting and alerting from the moment the service starts.
You can verify the engine is running with:
systemctl status crowdsec
Installing the nftables firewall bouncer
The bouncer is the component that actually blocks traffic. Without it, CrowdSec detects attacks and raises alerts but takes no enforcement action. On current Ubuntu releases the correct bouncer is crowdsec-firewall-bouncer-nftables, which replaces the older iptables-based bouncer that is now deprecated. The nftables bouncer creates and manages its own nftables ruleset and integrates cleanly with UFW if you’re using that for your baseline firewall rules.
apt-get install -y crowdsec-firewall-bouncer-nftables
/etc/crowdsec/bouncers/crowdsec-firewall-bouncer.yaml after installation and verify the api_key field contains an actual key rather than an empty value or placeholder string.If the API key is missing or shows a placeholder, you can generate a new one and patch it manually:
cscli bouncers delete crowdsec-firewall-bouncer-nftables
BOUNCER_KEY=$(cscli bouncers add crowdsec-firewall-bouncer-auto -o raw)
sed -i "s|api_key:.*|api_key: ${BOUNCER_KEY}|" /etc/crowdsec/bouncers/crowdsec-firewall-bouncer.yaml
Then confirm the bouncer configuration is valid before starting the service:
crowdsec-firewall-bouncer -c /etc/crowdsec/bouncers/crowdsec-firewall-bouncer.yaml -t
systemctl enable --now crowdsec-firewall-bouncer
With the bouncer running, banned IPs are actively blocked at the firewall level. The nftables ruleset CrowdSec creates drops packets from banned IPs before they reach your application layer, which means Nginx never even sees the request.
The collections we install on every CloudSonic server
CrowdSec organises its detection logic into collections, which are curated bundles of parsers and scenarios maintained by the CrowdSec team and the community. A parser teaches CrowdSec how to read a particular log format. A scenario defines what pattern of parsed log entries should trigger a ban. Installing a collection gives you both for a particular technology stack.
We install three collections on every server. The Nginx collection is essential because CrowdSec needs to understand Nginx log format to parse HTTP traffic at all. Without it, the HTTP log files get read but not understood, and none of the HTTP-based scenarios can fire.
cscli collections install crowdsecurity/nginx
The WordPress collection adds scenario detection specifically for WordPress attack patterns: login brute forcing against wp-login.php, user enumeration via the REST API, wp-config.php probing, and XML-RPC abuse. Even on servers that don’t host WordPress directly, this collection is worth installing because attackers scan everything for WordPress endpoints regardless of whether the site runs it. Our production data shows 331 WordPress brute force scenario overflows on a server with minimal traffic.
cscli collections install crowdsecurity/wordpress
The HTTP CVE collection adds detection for known CVE exploits being actively scanned in the wild. This covers things like path traversal exploits, PHP-FPM vulnerabilities, and remote code execution attempts against common web software. The scenario list grows as new CVEs are weaponised, and collection updates arrive automatically.
cscli collections install crowdsecurity/http-cve
After installing collections, reload CrowdSec to activate them:
systemctl reload crowdsec
Whitelisting your private network
Before CrowdSec starts blocking traffic in earnest, you need to make sure your own access methods are whitelisted. If you connect to your servers over a private network, VPN, or Tailscale mesh, those IP ranges should be excluded from CrowdSec’s detection engine entirely. A misconfigured deployment script or a curl loop that hits your server too aggressively could otherwise get your own IP banned, which is an unpleasant experience when you’re trying to log in to fix something.
For Tailscale, the relevant range is the CGNAT block assigned by the Tailscale coordination server. All Tailscale node IPs fall within this range regardless of which Tailscale account or tailnet they belong to, so whitelisting it covers all Tailscale-originated traffic.
cat > /etc/crowdsec/parsers/s02-enrich/whitelists.yaml << 'EOF'
name: crowdsecurity/whitelists
description: Whitelist Tailscale network
whitelist:
reason: "Tailscale private network"
cidr:
- "100.64.0.0/10"
EOF
systemctl reload crowdsec
CrowdSec also ships with whitelists for CDN providers, public DNS resolvers, and legitimate search engine crawlers. These are part of the default installation and mean that Googlebot and Cloudflare's IP ranges won't trigger bans even if they make a large number of requests. Our metrics show the CDN whitelist firing 1,191 times and passing 28 requests through that would otherwise have triggered scenarios, which on a production server with Cloudflare in front would be a much larger number.
What CrowdSec is actually blocking in production
The best way to understand what CrowdSec does is to look at what it catches on a real server. The following metrics come from one of our smaller infrastructure servers with relatively light traffic. A shared hosting server or a long-running VPS with an established IP reputation will see significantly higher numbers across every category.
The acquisition metrics show CrowdSec reading and parsing logs across every monitored source. The Nginx access log alone generated 180,000 lines, of which every single one was successfully parsed. The auth log shows the SSH picture: 25,300 lines read, with 7,740 parsed as meaningful authentication events that feed into the SSH brute force scenarios.
╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Acquisition Metrics │
├───────────────────────────────────────────────────────────┬────────────┬──────────────┬────────────────┬────────────────────────┬───────────────────┤
│ Source │ Lines read │ Lines parsed │ Lines unparsed │ Lines poured to bucket │ Lines whitelisted │
├───────────────────────────────────────────────────────────┼────────────┼──────────────┼────────────────┼────────────────────────┼───────────────────┤
│ file:/var/log/auth.log │ 25.30k │ 7.74k │ 17.57k │ 32.45k │ 67 │
│ file:/var/log/nginx/access.log │ 180.41k │ 180.41k │ - │ 81.85k │ - │
│ file:/var/log/nginx/error.log │ 627 │ 255 │ 372 │ 226 │ - │
╰───────────────────────────────────────────────────────────┴────────────┴──────────────┴────────────────┴────────────────────────┴───────────────────╯
The alerts table is where it gets interesting. These are the scenarios that fired and generated ban decisions during the monitoring period. The numbers look significant for a low-traffic server, and they are.
╭────────────────────────────────────────────────────╮
│ Local API Alerts │
├────────────────────────────────────────────┬───────┤
│ Reason │ Count │
├────────────────────────────────────────────┼───────┤
│ crowdsecurity/http-bad-user-agent │ 45 │
│ crowdsecurity/http-cve-2021-41773 │ 30 │
│ crowdsecurity/http-probing │ 29 │
│ crowdsecurity/CVE-2017-9841 │ 19 │
│ crowdsecurity/http-cve-2021-42013 │ 19 │
│ crowdsecurity/http-admin-interface-probing │ 10 │
│ crowdsecurity/http-open-proxy │ 10 │
│ crowdsecurity/nginx-req-limit-exceeded │ 6 │
│ crowdsecurity/http-sensitive-files │ 6 │
│ crowdsecurity/ssh-time-based-bf │ 5 │
│ crowdsecurity/ssh-bf │ 5 │
│ crowdsecurity/http-technology-probing │ 7 │
│ crowdsecurity/http-wordpress-scan │ 3 │
│ crowdsecurity/CVE-2022-41082 │ 2 │
│ crowdsecurity/ssh-time-based-bf_user-enum │ 2 │
│ crowdsecurity/http-crawl-non_statics │ 2 │
│ crowdsecurity/CVE-2019-18935 │ 1 │
│ crowdsecurity/ssh-slow-bf │ 4 │
╰────────────────────────────────────────────┴───────╯
The top entry, http-bad-user-agent, catches bots and scanners that identify themselves with known malicious or suspicious user agent strings. These are automated tools that make no attempt to disguise themselves because most servers don't check. http-cve-2021-41773 and http-cve-2021-42013 are path traversal exploits targeting Apache HTTP Server that were patched in late 2021 and are still being actively scanned for in 2026 because a meaningful number of servers never got updated. We're running Nginx, not Apache, so these requests can never succeed, but they still arrive constantly.
CVE-2017-9841 deserves a special mention. This is a remote code execution vulnerability in PHPUnit that was disclosed in 2017 and has been actively weaponised ever since. It targets a test endpoint at /vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php that should never exist on a production server. CrowdSec fired on this 19 times and the scenario metrics show 236 overflow events, meaning 236 separate IPs triggered this detection. This server does not run PHP. The attackers don't care. They scan every IP on the internet looking for anything vulnerable and let the automation sort out what sticks. CrowdSec banned all of them.
The SSH picture is also worth examining closely. Three separate brute force scenarios fired: ssh-bf for standard rapid attempts, ssh-time-based-bf for attempts spaced to evade rate limiting, and ssh-slow-bf for very low and slow attempts designed to fly under the radar of traditional tools. These are not three different attackers independently choosing different strategies. Modern SSH brute force toolkits run all three modes simultaneously and rotate between them. A Fail2ban installation with a standard retry threshold would catch the aggressive attempts and completely miss the slow ones. CrowdSec catches all three because each scenario uses different detection logic tuned for its specific timing pattern.
The bouncer metrics show what all of this detection translates to at the network level:
╭────────────────────────────────────────────────────────────────────────────────────╮
│ Bouncer Metrics │
├────────────────────────────────┬──────────────────┬─────────────────┬──────────────┤
│ Origin │ active_decisions │ dropped │ processed │
│ │ IPs │ bytes │ packets │ bytes│packets │
├────────────────────────────────┼──────────────────┼───────┼─────────┼──────┼───────┤
│ CAPI (community blocklist) │ 21.44k │ 1.32M │ 28.03k │ - │ - │
│ crowdsec (security engine) │ 2 │ 3.32M │ 27.68k │ - │ - │
├────────────────────────────────┼──────────────────┼───────┼─────────┼──────┼───────┤
│ Total │ 21.45k │ 4.63M │ 55.72k │15.25G│17.78M │
╰────────────────────────────────┴──────────────────┴───────┴─────────┴──────┴───────╯
The number that stands out here is 21,440 IPs currently blocked from the CAPI community blocklist. These are IPs that other CrowdSec installations around the world have reported as malicious and that the community has validated. Every single one of them is blocked on this server before they make their first request, before CrowdSec has seen a single packet from them in its own logs. That is the fundamental advantage over Fail2ban: the community blocklist means your server benefits from the collective experience of every other CrowdSec installation, not just its own history.
The bouncer has dropped 55,720 packets totalling 4.63 megabytes of blocked traffic since August 12. That is not a large number in absolute terms because this is a low-traffic infrastructure server. On a shared hosting server running dozens of WordPress sites, or an older server with an IP that has years of attack history attached to it, these numbers would be orders of magnitude higher. The community blocklist alone scales with the size and age of your server's exposure surface in a way that a locally-learned blocklist like Fail2ban never can.
Reading the scenario metrics
The scenario metrics table shows the lifetime activity of every detection scenario, including how many buckets were instantiated, how many overflowed into alerts, and how many expired without reaching the threshold. Understanding this table helps you tune your setup and identify which attack types are most active against your specific server.
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Scenario Metrics │
├────────────────────────────────────────────┬───────────────┬───────────┬──────────────┬────────┬─────────┤
│ Scenario │ Current Count │ Overflows │ Instantiated │ Poured │ Expired │
├────────────────────────────────────────────┼───────────────┼───────────┼──────────────┼────────┼─────────┤
│ crowdsecurity/CVE-2017-9841 │ - │ 236 │ 236 │ - │ - │
│ crowdsecurity/http-bf-wordpress_bf │ - │ 331 │ 549 │ 2.27k │ 218 │
│ crowdsecurity/http-bad-user-agent │ - │ 213 │ 476 │ 692 │ 263 │
│ crowdsecurity/http-sensitive-files │ 1 │ 222 │ 477 │ 1.69k │ 254 │
│ crowdsecurity/http-probing │ 3 │ 202 │ 2.34k │ 6.39k │ 2.13k │
│ crowdsecurity/nginx-req-limit-exceeded │ - │ 347 │ 380 │ 2.18k │ 33 │
│ crowdsecurity/ssh-time-based-bf │ 15 │ 331 │ 1.82k │ 7.64k │ 1.48k │
│ crowdsecurity/ssh-bf │ - │ 7 │ 3.27k │ 7.64k │ 3.27k │
│ crowdsecurity/ssh-slow-bf │ 4 │ 111 │ 2.27k │ 7.64k │ 2.15k │
│ crowdsecurity/http-wordpress-scan │ - │ 71 │ 154 │ 379 │ 83 │
│ crowdsecurity/http-admin-interface-probing │ - │ 112 │ 211 │ 473 │ 99 │
│ crowdsecurity/http-cve-2021-41773 │ - │ 90 │ 90 │ - │ - │
│ crowdsecurity/http-cve-2021-42013 │ - │ 55 │ 55 │ - │ - │
│ crowdsecurity/http-open-proxy │ - │ 48 │ 48 │ - │ - │
╰────────────────────────────────────────────┴───────────────┴───────────┴──────────────┴────────┴─────────╯
The Overflows column is the number of times a scenario bucket filled up and triggered a ban decision. The Instantiated column is how many buckets were created, meaning how many distinct IPs triggered at least one event matching that scenario. The Expired column shows IPs that started down the path toward a ban but didn't generate enough events within the scenario's time window to cross the threshold. For ssh-bf, 3,270 IPs instantiated a bucket but only 7 overflowed into actual bans, because the CAPI community blocklist had already banned the rest before they generated enough local events to trigger a local ban decision. That's the community blocklist doing its job.
The http-bf-wordpress_bf scenario shows 331 overflows from 549 instantiated buckets, meaning 331 IPs hit the WordPress login brute force threshold and got banned. There is no WordPress running on this server. The 218 expired buckets are IPs that probed for WordPress endpoints but didn't hit them frequently enough to cross the ban threshold before CrowdSec's bucket timer ran out. Many of those will already be on the CAPI blocklist from their activity on other servers.
Verifying your setup with cscli
Once everything is running, a handful of cscli commands let you verify the setup is healthy and see what decisions are currently active.
Check that the bouncer is registered and communicating with the local API:
cscli bouncers list
Check currently active ban decisions, which shows what IPs are blocked right now and why:
cscli decisions list
Check recent alerts, which shows every scenario that fired and generated a decision in the recent history window:
cscli alerts list
Pull the full metrics output to see parser activity, scenario counts, bouncer packet drops, and whitelist hits all in one place:
sudo cscli metrics
If you want to test that the bouncer is actually enforcing decisions at the firewall level, you can add a manual ban for a test IP and verify it appears in the nftables ruleset:
cscli decisions add --ip 198.51.100.1 --duration 5m --reason "testing bouncer"
nft list ruleset | grep 198.51.100.1
cscli decisions delete --ip 198.51.100.1
The IP 198.51.100.1 is from the TEST-NET-2 range reserved for documentation and will never belong to a real server, so this is safe to use for testing without accidentally blocking a legitimate IP.
The internet is genuinely hostile in a way that makes Fail2ban's isolated, reactive model feel dated. Every server gets probed constantly, by scanners that already know which CVEs to try, by SSH brute forcers running multiple timing strategies in parallel, and by botnets that have been building target lists for years. CrowdSec doesn't eliminate that noise, but it means your server isn't facing it alone.