Check your auth log on any server that's been public for more than a day:
sudo grep "Failed password" /var/log/auth.log | wc -l
Thousands, probably. Bots hammer SSH 24/7 — they're not targeted at you specifically, they scan the entire IPv4 space continuously. fail2ban watches the logs and bans IPs that fail too many times.
It won't stop a sophisticated targeted attack, but it kills the automated noise and keeps your logs readable.
Install
sudo apt update
sudo apt install -y fail2ban
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
Don't edit jail.conf
/etc/fail2ban/jail.conf gets overwritten on updates. All your config goes in /etc/fail2ban/jail.local — which takes precedence and survives upgrades.
sudo nano /etc/fail2ban/jail.local
My jail.local
[DEFAULT]
# Ban IPs for 1 hour
bantime = 3600
# Time window to count failures
findtime = 600
# Number of failures before ban
maxretry = 5
# Your own IPs - never ban these
ignoreip = 127.0.0.1/8 ::1
[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
backend = %(syslog_backend)s
maxretry = 3
[nginx-http-auth]
enabled = true
logpath = /var/log/nginx/error.log
[nginx-botsearch]
enabled = true
logpath = /var/log/nginx/access.log
maxretry = 2
I use maxretry = 3 for SSH — three wrong passwords and you're out for an hour. For bots scanning for WordPress admin panels and PHP files on a server that has neither, two hits is enough.
If you're on a non-standard SSH port, change the port line:
[sshd]
enabled = true
port = 2222
Apply and check
sudo systemctl restart fail2ban
sudo fail2ban-client status
You should see your enabled jails listed. Check a specific jail:
sudo fail2ban-client status sshd
Shows currently banned IPs, total bans, failed attempts. If you see zeroes across the board and you know there's attack traffic, the logpath is probably wrong — see troubleshooting below.
Add your own IP to ignoreip before testing
Seriously. I've locked myself out doing exactly this — running ssh user@server with the wrong key three times while setting up fail2ban. Add your home/office IP to ignoreip before you do anything:
ignoreip = 127.0.0.1/8 ::1 203.0.113.42
Manually ban and unban
# Ban an IP immediately
sudo fail2ban-client set sshd banip 1.2.3.4
# Unban an IP
sudo fail2ban-client set sshd unbanip 1.2.3.4
# Check if an IP is banned
sudo fail2ban-client get sshd banned | grep 1.2.3.4
Make bans persistent across reboots
By default fail2ban loses its ban list on restart. Enable the database:
sudo nano /etc/fail2ban/fail2ban.local
[Definition]
dbfile = /var/lib/fail2ban/fail2ban.sqlite3
dbpurgeage = 86400
dbpurgeage is how long to keep ban records — 86400 seconds = 24 hours. Tune to taste.
Troubleshooting: jail shows no bans despite attack traffic
Wrong log path — the most common issue. Verify the log actually exists and has content:
sudo tail /var/log/auth.log
sudo tail /var/log/nginx/access.log
On systems using systemd journald instead of flat log files, the sshd jail needs:
[sshd]
backend = systemd
Wrong log format — fail2ban uses regex filters to parse logs. If your Nginx log format is non-standard, the filter won't match. Check what filter is being used:
sudo fail2ban-client get nginx-botsearch logpath
cat /etc/fail2ban/filter.d/nginx-botsearch.conf
Test a filter manually:
sudo fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/nginx-botsearch.conf
Shows how many lines matched. Zero matches = filter or log format problem.
What the logs look like when it's working
sudo tail -f /var/log/fail2ban.log
2026-04-25 03:14:28 INFO [sshd] Found 91.234.55.12 - 2026-04-25 03:14:28
2026-04-25 03:14:31 INFO [sshd] Found 91.234.55.12 - 2026-04-25 03:14:31
2026-04-25 03:14:34 INFO [sshd] Found 91.234.55.12 - 2026-04-25 03:14:34
2026-04-25 03:14:34 NOTICE [sshd] Ban 91.234.55.12
Three failures, ban. Exactly what you want to see.
Longer bans for repeat offenders
fail2ban 0.10+ supports incremental ban times. Add to [DEFAULT] in jail.local:
bantime.increment = true
bantime.multiplier = 2
bantime.maxtime = 86400
First offense: 1h. Second: 2h. Third: 4h. Caps at 24h. Repeat scanners escalate automatically.