IAnswerable

Block SSH Brute Force Attacks with fail2ban

10 Nov 2025

Check /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

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.