Your SSH Port Is Getting Hammered — the Fail2ban vs CrowdSec Playbook
Every public Linux server absorbs thousands of SSH login attempts a day — bots cycling through root, admin, and oracle with a dictionary of terrible passwords. Here's how to silence them: a tested Fail2ban setup, the mistakes that lock you out, and when to graduate to CrowdSec's community blocklist.
The first thing a new VPS owner should do after the first deploy isn't installing a dashboard. It's this:
journalctl -u ssh --since "24 hours ago" | grep -c "Failed password"
On a fresh box, that number is already in the hundreds. Within a week, thousands. None of it is personal — it's the background radiation of the internet, botnets sweeping every IPv4 address for weak SSH. But ignoring it costs you: log disk, CPU cycles, and a haystack that hides the real break-in attempt when it finally comes.
The fix is a bouncer at the door. Two tools dominate this job in 2026: the venerable Fail2ban, and the collaborative CrowdSec. Here's how an operator actually deploys them.
Fail2ban: a bouncer, not a bodyguard
Fail2ban watches your logs, counts failures, and injects firewall rules to drop the offender. That's all it does — and all it should do. It is not a substitute for real authentication hygiene: if you haven't switched to SSH keys and disabled password auth yet, do that first (the full drill is in my SSH hardening audit playbook). Fail2ban sits on top of a hardened SSH, not instead of it.
Step 1: install, then keep your hands off jail.conf
sudo apt install -y fail2ban
sudo systemctl enable --now fail2ban
The golden rule of Fail2ban configuration: never edit /etc/fail2ban/jail.conf. It's package-owned and gets overwritten on updates. Your settings go in /etc/fail2ban/jail.local or drop-in files under /etc/fail2ban/jail.d/. I've seen more than one server lose its entire ban policy to a routine apt upgrade because someone edited the shipped file directly.
Step 2: whitelist yourself before anything else
Before enabling any jail, find the IP you're connecting from:
echo "$SSH_CLIENT"
Then make sure it can never be banned. The single most common Fail2ban war story is the operator who typo'd a password three times and locked themselves out of their own box. Your own drop-in, /etc/fail2ban/jail.d/sshd.local:
[sshd]
enabled = true
backend = systemd
maxretry = 5
findtime = 10m
bantime = 1h
ignoreip = 127.0.0.1/8 ::1 203.0.113.10
Replace 203.0.113.10 with your real address. On modern distros (Debian 12/13, Ubuntu 24.04) sshd logs to the systemd journal and /var/log/auth.log may not even exist — backend = systemd makes Fail2ban read the journal directly instead of chasing a log file that's going nowhere. One more line matters if you moved SSH off port 22: set port = 2222 (or whatever you use) explicitly, because the default action builds its firewall rule around port = ssh.
Step 3: make repeat offenders regret it
Five failures in ten minutes buys one hour of silence. That's fine for casual scanners, but persistent botnets just come back. Two upgrades turn the default into a real deterrent.
First, incremental bans — every repeat offense multiplies the ban length:
[DEFAULT]
bantime.increment = true
Second, the recidive jail — it watches Fail2ban's own log and drops a long ban on any IP that's been banned by several jails in a day:
[recidive]
enabled = true
logpath = /var/log/fail2ban.log
bantime = 1w
findtime = 1d
maxretry = 5
Apply with sudo fail2ban-client reload — a reload keeps existing bans; a full restart wipes them. Then confirm the jail is alive: sudo fail2ban-client status sshd.
One firewall note: pick the ban action that matches your stack. On an nftables system, banaction = nftables-multiport; on UFW, banaction = ufw; on firewalld, banaction = firewallcmd-ipset. The wrong backend silently fails — Fail2ban reports a ban while the packets keep flowing. Check with nft list ruleset | grep f2b after your first real ban lands.
The traps, learned the hard way
The lockout. Keep a second SSH session open while you reload Fail2ban. If session one survives, your config is safe.
The custom port. Moving SSH to a high port cuts noise dramatically, but if Fail2ban still thinks the port is ssh, its firewall rule blocks the wrong port. Always set port explicitly.
The proxy in front. Fail2ban bans the IP it sees connecting. For SSH that's the real client — fine. But if you enable web jails behind a reverse proxy or CDN, you'll ban the proxy's IP and nuke all your traffic. For web, get the real client IP first, then jail.
The untested filter. Before trusting any custom filter, run it against real logs: sudo fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/nginx-http-auth.conf. A filter that matches nothing is a bouncer asleep at the door.
When one box's logs aren't enough: CrowdSec
Here's Fail2ban's fundamental limit: it only knows what your box has seen. The bot that just finished hammering 5,000 other servers gets a fresh start on yours. CrowdSec fixes this with collaboration — every installation shares its attack signals, so known-bad IPs arrive on the community blocklist and your box drops them before their first attempt against you.
The architecture has four parts: the engine (reads logs, runs detection scenarios), a bouncer (enforces decisions in your firewall), the hub (community-maintained parser/scenario collections), and the central API that distributes the blocklist.
Install from the official repo:
curl -s https://install.crowdsec.net | sudo bash
sudo apt install -y crowdsec crowdsec-firewall-bouncer
The installer auto-detects your services. Verify SSH is covered:
sudo cscli collections list
You want crowdsecurity/linux and crowdsecurity/sshd enabled. If sshd wasn't detected, add it: sudo cscli collections install crowdsecurity/sshd && sudo systemctl reload crowdsec. Then watch the decisions roll in: sudo cscli decisions list shows every currently banned IP, why, and for how long; sudo cscli metrics shows what's being parsed.
Three gotchas from real deployments. First, the local API listens on port 8080 by default — if something already owns that port, the engine won't start; change listen_uri in /etc/crowdsec/config.yaml (and the matching credentials file). Second, whitelist your own IP in /etc/crowdsec/parsers/s02-enrich/my-whitelists.yaml — the same lockout story applies here, with worse consequences since CrowdSec bans last longer. Third: pick one enforcer. If you run Fail2ban and CrowdSec on the same box, make sure only one manages a given firewall chain, or the two will fight over rules and you'll debug phantom bans for hours.
Which one should you run?
For a single VPS with SSH as the main exposure: Fail2ban. Fifteen minutes of config, battle-tested for two decades, zero network dependencies.
For a box running public web apps, mail, or a small fleet: CrowdSec. The community blocklist is doing real work the moment you connect it — blocking scanners your box has never met — and the hub's scenario collections cover nginx, apache, and database services without you writing a single regex.
Either way, the principle is the same: a public port without an automated bouncer is a door with a sign that says "please knock." Thousands of strangers are knocking. Give the door a brain.
Sources
- Fail2ban official repository and manual — https://github.com/fail2ban/fail2ban
- CrowdSec documentation — https://docs.crowdsec.net
- CrowdSec Hub (collections catalog) — https://app.crowdsec.net
Reader feedback
Was this article helpful?
0 people found this article helpful.
