Every public VPS faces thousands of SSH password guessing attempts every day. Fail2ban analyses logs and automatically blocks attacking IP addresses.
Installing Fail2ban
sudo apt update
sudo apt install -y fail2ban
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
Setting up SSH protection
Create a local config (do not edit jail.conf — it gets overwritten on updates):
sudo nano /etc/fail2ban/jail.local
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 5
ignoreip = 127.0.0.1/8 YOUR_HOME_IP
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 86400
sudo systemctl restart fail2ban
What each parameter means
| Parameter | Value in the example | Meaning |
|---|---|---|
| bantime | 3600 (86400 for sshd) | How many seconds an IP is blocked after exceeding the limit |
| findtime | 600 | The time window in seconds over which failed attempts are counted |
| maxretry | 5 (3 for sshd) | How many failed attempts are allowed within findtime |
| ignoreip | 127.0.0.1/8 and your IP | Addresses that are never blocked |
The [sshd] section overrides maxretry and bantime from [DEFAULT] — for SSH they are usually made stricter (fewer attempts, a longer ban) than the general default for other services.
Common configuration mistakes
Wrong logpath for the distribution
The path /var/log/auth.log is correct for Debian and Ubuntu. On CentOS, AlmaLinux and other RHEL-like distributions the authentication log lives at /var/log/secure — with the wrong path Fail2ban starts without an error, but the jail never triggers because it simply cannot find a file to read.
# Check that the jail is actually reading the log file:
sudo fail2ban-client status sshd
# The "File list" line should show a real log path with data in it,
# not the path to an empty or nonexistent file
ignoreip without your own IP
If you do not add your working IP to ignoreip and the maxretry threshold is low, ordinary password typos during a normal login can ban you for the bantime period. This is especially dangerous with a dynamic home IP — check the current address every time your provider reconnects you.
On systems where the authentication log is kept only through the systemd journal, with no /var/log/auth.log or /var/log/secure file (some minimal images and container environments), a regular logpath will not work at all. For that case the jail has a separate backend parameter that reads entries directly from the journal instead of a file on disk:
[sshd]
enabled = true
backend = systemd
maxretry = 3
Basic commands
# Status of all jails
sudo fail2ban-client status
# SSH jail status
sudo fail2ban-client status sshd
# Manually unban an IP
sudo fail2ban-client set sshd unbanip 203.0.113.42
# View blocked IPs
sudo iptables -L f2b-sshd -n --line-numbers
Protecting Nginx from DDoS and scanning
# Add to jail.local:
[nginx-http-auth]
enabled = true
logpath = /var/log/nginx/error.log
[nginx-botsearch]
enabled = true
logpath = /var/log/nginx/access.log
maxretry = 2
/etc/ssh/sshd_config — this cuts the number of attacks by 95%.sudo ufw allow 2222/tcpWhen Fail2ban will not help
- A distributed brute force from thousands of different IPs, one or two attempts from each — the
maxretrythreshold for an individual address never triggers because no single IP exceeds it. - An attack through a vulnerability in the application itself rather than password guessing — Fail2ban reacts to patterns in failed-authentication logs, not to arbitrary HTTP requests with no matching filter signature.
- Logging in with a compromised but valid password or key on the first try — there is nothing to block based on repeated failures here; you need two-factor authentication and monitoring for suspicious logins.
Checking that the protection works
- Run
sudo fail2ban-client status sshd— the output should show a correct log path and a growingCurrently failedcounter after failed login attempts. - From another IP (not in
ignoreip), deliberately enter a wrong SSH password more thanmaxretrytimes in a row — the next attempt should be rejected by the firewall, not the login form. - Check the ban list:
sudo fail2ban-client status sshdshould show that IP in theBanned IP list. - Unban the test IP with
fail2ban-client set sshd unbanipand confirm the connection goes through again — this confirms the ban lifts normally, not only by hand through iptables.
Setting up the firewall that Fail2ban places its blocking rules on is covered in UFW for Ubuntu and Debian, and logging in with keys instead of a password removes the need for guessing entirely — see hardening SSH on a server.
Installation
sudo apt update
sudo apt install fail2ban -y
sudo systemctl enable --now fail2ban
Creating the jail.local Config
Never edit jail.conf — it gets overwritten on updates. Create jail.local:
># /etc/fail2ban/jail.local
[DEFAULT]
# Ban time in seconds (3600 = 1 hour, -1 = forever)
bantime = 3600
# Time window to look for attempts (10 minutes)
findtime = 600
# Max attempts before a ban
maxretry = 5
# Exclude your own IP from bans!
ignoreip = 127.0.0.1/8 YOUR.TRUSTED.IP
[sshd]
enabled = true
port = 22
logpath = %(sshd_log)s
maxretry = 3
[nginx-http-auth]
enabled = true
port = http,https
logpath = /var/log/nginx/error.log
maxretry = 5
[postfix-sasl]
enabled = true
port = smtp,submission
logpath = /var/log/mail.log
maxretry = 5
sudo systemctl restart fail2ban
Management and Monitoring
# General status
sudo fail2ban-client status
# Status of a specific jail
sudo fail2ban-client status sshd
# Manually ban an IP
sudo fail2ban-client set sshd banip 192.168.1.100
# Unban an IP
sudo fail2ban-client set sshd unbanip 192.168.1.100
# View banned IPs in iptables
sudo iptables -L f2b-sshd -n -v
# Fail2Ban logs
sudo journalctl -u fail2ban -f
Real-Time Attack Monitoring
# Watch SSH bans in real time
sudo tail -f /var/log/auth.log | grep "Failed password"
# Top attacking IPs
sudo grep "Failed password" /var/log/auth.log | awk '{print $(NF-3)}' | sort | uniq -c | sort -rn | head -20