Block SSH Brute Force Attacks with fail2ban
10 Nov 2025Check /var/log/auth.log on any internet-facing VPS and you’ll find hundreds of failed SSH login attempts within the first day, all from bots trying common usernames and password lists. Key-only auth already stops them from getting in. fail2ban stops them from trying forever, by banning the IP after a handful of failures.
What it actually does
fail2ban watches log files for patterns that indicate malicious activity — repeated failed logins is the classic case — and when it sees too many within a time window, it adds a firewall rule blocking that IP for a set duration. It doesn’t prevent a determined, distributed attack. It does stop the constant background noise of scanners from ever getting more than a few attempts before they’re locked out, and it keeps your logs from being wall-to-wall noise when you’re trying to find something real.
1. Install it
sudo apt install fail2ban -y
sudo systemctl enable --now fail2ban
On most Debian/Ubuntu installs, an SSH jail is enabled by default the moment fail2ban starts, using the distro’s default config. Confirm:
sudo fail2ban-client status
sudo fail2ban-client status sshd
The second command shows current bans and the count of failed attempts it’s tracked.
2. Don’t edit the default config file directly
/etc/fail2ban/jail.conf gets overwritten on package updates. Any customization goes in a .local override instead:
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local
fail2ban reads both and lets .local values win, so your changes survive updates.
3. Tune the ban policy
Inside the [sshd] section (or [DEFAULT] for global defaults):
[sshd]
enabled = true
port = ssh
maxretry = 5
findtime = 10m
bantime = 1h
maxretry— failed attempts allowed before a banfindtime— the window those attempts have to happen inbantime— how long the ban lasts
A stricter setup for a server that really doesn’t need to tolerate probing:
maxretry = 3
findtime = 10m
bantime = 24h
If you’re worried about banning yourself for too long after a typo’d password, set bantime.increment = true — repeat offenders get progressively longer bans, but a one-off slip doesn’t cost you a full day.
4. If you changed the SSH port
If you moved SSH off 22 as part of your hardening, update the jail:
[sshd]
port = 2222
fail2ban reads the log entries either way, but the port setting controls which port it blocks on the firewall side — get this wrong and it bans IPs on the wrong port, doing nothing useful.
5. Verify it’s really banning
Don’t just trust that it’s configured — check after a deploy or config change:
sudo fail2ban-client status sshd
Look for a nonzero “Currently banned” count after normal internet exposure (give it a day). If it’s always zero on a public-facing box, something’s wrong — either the jail isn’t enabled, or it’s not matching your log format.
The gotcha: log format mismatches
fail2ban matches log lines against regex filters. If you’re running SSH inside a container, using a non-standard log format, or your distro logs to journald instead of a flat file, the default filter can silently fail to match anything — fail2ban runs, reports “active,” and bans nobody, because it never sees a failure it recognizes.
Test a filter directly against your actual log source:
sudo fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf
This tells you how many log lines matched. Zero matches on a server you know has had failed logins means your log path or format doesn’t match what the filter expects — check sudo journalctl -u ssh | tail versus what’s actually in /var/log/auth.log, and point the jail’s logpath at whichever one is real.
The takeaway
Install it, confirm the sshd jail is active, and check the ban count after a day of real internet exposure. If it’s zero when it shouldn’t be, it’s a log-matching problem, not a “nobody’s attacking me” situation — every public IP gets probed.