Your server wakes up one morning with 47,000 SSH login attempts from three continents. You didn’t sleep. Your logs are screaming. Sound familiar?
Welcome to self-hosting in 2026. The bots are relentless, and they’re not even trying to be subtle. Brute-force attacks on SSH, web scrapers hammering your WordPress REST API, credential stuffing against your Nextcloud login — this is Tuesday.
Two tools have your back here: Fail2ban and CrowdSec. They’re not enemies. They’re not even quite the same thing. But understanding the difference between “blocking IPs that attacked you” and “blocking IPs before they attack you” will save your sanity.
The Threat Landscape: Why You Need Anything At All
Let me paint the picture. Right now, thousands of IP addresses are running automated scans. They’re looking for:
- Open SSH ports with weak passwords (still works shockingly often)
- WordPress
/xmlrpc.phpendpoints (hello, 2009) - Unpatched Nextcloud auth endpoints
- Exposed Docker APIs on port 2375
- Default credentials on anything and everything
These aren’t sophisticated. They’re not targeting you specifically. They’re just scanning the entire internet, throwing spaghetti at walls, and checking what sticks. But they’re persistent. And they’re everywhere.
The goal of any intrusion prevention system: stop the noise before it drains your server, and ideally, stop the attack before it even gets close.
Fail2ban: The Classic Approach
Fail2ban’s been around since 2004, and it does one thing very well: it reads your logs, spots patterns of bad behavior, and bans the IP address doing it.
Here’s the flow:
- Attacker tries SSH brute force → creates failed login entries in
/var/log/auth.log - Fail2ban regex watches that log in real-time
- After 5 failed attempts in 10 minutes → ban the IP for 24 hours
- Ban = firewall rule (iptables/nftables) or Cloudflare API call
It’s reactive. It waits for the attack, detects it, then hits the kill switch. By that point, your server’s already been poked. Not ideal, but effective.
Installing and Configuring Fail2ban
sudo apt update && sudo apt install fail2bansudo systemctl enable fail2bansudo systemctl start fail2banCreate a local override for the default SSH jail:
[DEFAULT]bantime = 86400findtime = 600maxretry = 5destemail = your-email@example.comsendername = Fail2banaction = %(action_mwl)s
[sshd]enabled = trueport = sshlogpath = %(sshd_log)sThis says: “If someone fails SSH auth 5 times in 10 minutes, ban them for 24 hours and email me about it.”
Check what’s been banned:
sudo fail2ban-client status sshdsudo iptables -L -n | grep DROP # see the actual firewall rulesUnban an IP if you fat-fingered it:
sudo fail2ban-client set sshd unbanip 203.0.113.45The catch: Fail2ban only knows about attacks it sees on your server. If a botnet is scanning the world and 10,000 servers are blocking them, Fail2ban doesn’t benefit from that collective intelligence. You’re flying blind, learning only from your own traffic.
CrowdSec: The Evolution
CrowdSec is what Fail2ban would build if it had friends.
Same core idea — parse logs, detect attacks, ban IPs. But with a twist: CrowdSec agents send anonymized attack telemetry back to a central community network. You get the global blocklist in return. You’re not just blocking attackers after they hit you; you’re blocking IPs that attacked someone else in Tokyo before they get to your server in Toronto.
How CrowdSec Works
┌─────────────────────────────────────────────────┐│ Your Server ││ ├─ CrowdSec Agent (reads logs) ││ └─ Bouncer (applies blocks: firewall/WAF) │└──────────────┬──────────────────────────────────┘ │ ┌───────▼────────┐ │ Central LAPI │ │ (community) │ └───────┬────────┘ │ ┌──────────┴──────────┐ │ Global blocklist │ │ + threat intel │ └─────────────────────┘Three pieces:
- Agent: Runs on your server, reads logs (SSH, Nginx, Apache, anything), detects attacks, shares telemetry
- LAPI (Local API): Central repository that syncs global threat intel to your local installation
- Bouncer: Enforces blocks (Nginx plugin, iptables, Cloudflare API, whatever)
Installing CrowdSec
curl -s https://packagecloud.io/install/repositories/crowdsec/crowdsec/script.deb.sh | sudo bashsudo apt install crowdsec crowdsec-firewall-bouncersudo systemctl enable crowdsecsudo systemctl start crowdsecEnroll to the community (free, optional, but recommended):
sudo cscli console enroll --overwrite <enrollment-key>Check what’s being blocked:
sudo cscli decisions listsudo cscli metrics showCrowdSec + Nginx Bouncer
If you’re running Nginx, install the bouncer:
sudo apt install crowdsec-nginx-bouncerConfigure Nginx to use it:
ENABLED=trueAPI_URL=http://localhost:8080API_KEY=<your-api-key>BOUNCER_LOG_LEVEL=infoAdd this to your Nginx config:
location / { access_by_lua_file /usr/local/lua/crowdsec_nginx_bouncer.lua; proxy_pass http://backend;}Reload Nginx:
sudo nginx -t && sudo systemctl reload nginxNow Nginx will block requests from IPs in the CrowdSec decision list before they even hit your app.
CrowdSec Dashboard and CLI
The community dashboard shows you global attacks:
sudo cscli dashboardRuns on http://localhost:3000 by default.
View active decisions:
sudo cscli decisions listsudo cscli alerts listManually add an IP to the blocklist:
sudo cscli decisions add --ip 203.0.113.99 --duration 24h --reason "manual block"Fail2ban + CrowdSec: Can You Run Both?
Absolutely. They don’t fight. Fail2ban bans locally, CrowdSec shares globally. In fact, they’re complementary:
- Fail2ban catches novel attacks that haven’t hit the CrowdSec network yet
- CrowdSec catches known attackers before they even get to your Fail2ban rules
Run both. Fail2ban first (it’s lighter), CrowdSec second (it’s smarter).
Handling False Positives
Fail2ban:
- Edit
/etc/fail2ban/jail.local, increasemaxretry, tunefindtime - Whitelist trusted IPs:
ignoreip = 127.0.0.1/8 ::1 203.0.113.50 - Check the log before banning:
sudo tail -f /var/log/fail2ban.log
CrowdSec:
- Review alerts:
sudo cscli alerts list - Whitelist legitimate traffic in scenarios
- False positives? Check the community forums — you might discover a misconfigured scenario
Decision Guide
Use Fail2ban alone if:
- You’re running a simple setup (single server, maybe a small app)
- You don’t want external dependencies
- You trust your log parsing to be good enough
- You’re on a tight resource budget
Add CrowdSec if:
- You’re tired of the noise (4 AM attacks on WordPress endpoints)
- You want to block threats before they touch your server
- You’re willing to send anonymized telemetry back to the community
- You’re running multiple services (Nginx, SSH, your custom API)
- You want visibility: dashboards, metrics, alert history
Run both if:
- You can afford the overhead (it’s minimal)
- You want defense in depth
- You want local + global intelligence
The Reality
Here’s the thing: bots are dumb. They’re not targeting you. They’re just running through IP ranges like a telemarketer dialing phone numbers. Fail2ban stops the ones that get to you. CrowdSec stops the ones that didn’t need to reach you at all.
Neither is magic. A real attacker with patience and a botnet spanning thousands of IPs will eventually get through any blacklist. But for the 99.9% of attacks — the commodity brute-force noise that wakes you up at 3 AM — both tools are brutally effective.
Set one up tonight. You’ll sleep better tomorrow.