2 AM and Your SSH Logs Are on Fire
I still remember the first time I checked /var/log/auth.log on a VPS I’d set up just three days earlier — over 4,000 SSH login attempts from different IPs within a 6-hour window. Not a single one succeeded. But just knowing they were hammering the server 24/7 was enough to keep me up at night.
Fail2ban is the first thing I install right after setting up SSH keys on any new server. After 20+ Ubuntu Server 22.04 VPS deployments, it’s become second nature. This guide gets straight to the point — installation, configuration, testing, and handling common scenarios.
How Does Fail2ban Work?
The mechanism is simpler than you might think: Fail2ban monitors log files (auth.log, nginx/access.log, etc.), detects IPs that fail authentication too many times within a defined window, and automatically adds iptables/nftables rules to block them.
Three core concepts:
- jail — a set of monitoring rules for a specific service (SSH, Nginx, etc.)
- filter — a regex pattern to identify “suspicious” log lines
- action — what to do when a violation is detected (ban IP, send email, etc.)
Fail2ban ships with built-in filters for dozens of popular services. In most cases, you just need to enable the relevant jail and you’re done.
Installing Fail2ban on Ubuntu
sudo apt update
sudo apt install fail2ban -y
After installation, Fail2ban starts automatically. Check its status:
sudo systemctl status fail2ban
You should see active (running). If not, run:
sudo systemctl enable --now fail2ban
Configuring It Right — Don’t Edit the Default File
Fail2ban has two configuration layers:
/etc/fail2ban/jail.conf— the default file, overwritten when the package updates/etc/fail2ban/jail.local— your override file, always takes priority
Rule of thumb: never edit jail.conf. Create jail.local from a copy:
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local
Configuring the [DEFAULT] Section
Settings here apply to all jails unless overridden in a specific jail:
[DEFAULT]
# List of IPs/subnets that will never be banned — IMPORTANT
ignoreip = 127.0.0.1/8 ::1 YOUR_HOME_IP
# Ban duration (seconds). -1 = permanent ban
bantime = 3600
# Time window for counting failures
findtime = 600
# Maximum failures before banning
maxretry = 5
# Log reading backend — systemd or auto
backend = systemd
Replace YOUR_HOME_IP with your actual IP. I once locked myself out of a server by skipping this step — not a fun situation at 11 PM with no console access.
Enabling the SSH Jail
On Ubuntu 22.04 with systemd, configure the SSH jail in jail.local as follows:
[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
backend = %(sshd_backend)s
maxretry = 3
bantime = 86400
I go with maxretry = 3 and bantime = 86400 (24 hours) for SSH — stricter than defaults because SSH is the number one target for every automated scanner.
Reload to apply:
sudo systemctl reload fail2ban
Verifying the Jails Are Running
# List all running jails
sudo fail2ban-client status
# Check sshd jail details
sudo fail2ban-client status sshd
The output of the last command shows currently banned IPs, total ban count, and the specific IP list:
Status for the jail: sshd
|- Filter
| |- Currently failed: 2
| |- Total failed: 47
| `- Journal matches: _SYSTEMD_UNIT=ssh.service + _COMM=sshd
`- Actions
|- Currently banned: 3
|- Total banned: 12
`- Banned IP list: 203.0.113.42 198.51.100.17 192.0.2.88
Real-World Usage: Protecting Nginx and WordPress
Brute-force attacks aren’t limited to SSH. If you’re running a web server, WordPress login and Nginx are also frequent targets — sometimes even noisier than SSH.
Nginx Jail — Blocking Scans and 4xx Floods
[nginx-http-auth]
enabled = true
port = http,https
logpath = /var/log/nginx/error.log
[nginx-botsearch]
enabled = true
port = http,https
logpath = /var/log/nginx/access.log
maxretry = 2
Custom Jail for WordPress xmlrpc.php
The xmlrpc.php file is an extremely common attack vector — frequently used to brute-force credentials via XML-RPC multicall. Create a custom filter:
sudo nano /etc/fail2ban/filter.d/wordpress-xmlrpc.conf
[Definition]
failregex = <HOST> .* "POST /xmlrpc.php
ignoreregex =
Then add the jail to jail.local:
[wordpress-xmlrpc]
enabled = true
port = http,https
filter = wordpress-xmlrpc
logpath = /var/log/nginx/access.log
maxretry = 5
bantime = 86400
Unbanning IPs and Debugging
Classic scenario: you’re testing SSH from another machine, accidentally mistype your password three times, and ban yourself. Quick fix:
# Unban a specific IP
sudo fail2ban-client set sshd unbanip 1.2.3.4
# Test whether the filter matches log lines correctly
sudo fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf
# View fail2ban logs
sudo tail -f /var/log/fail2ban.log
The fail2ban-regex command is especially useful when writing custom filters — it tells you exactly how many log lines match your pattern, saving a lot of debugging time.
Automatically Escalating Ban Times for Repeat Offenders
Repeat offenders should face harsher penalties. Fail2ban supports bantime.increment for exactly this — each subsequent violation doubles the ban time. Add this to the [DEFAULT] section:
[DEFAULT]
bantime.increment = true
bantime.factor = 1
bantime.formula = ban.Time * (1<<(ban.Count if ban.Count<20 else 20)) * banFactor
bantime.multiplier = 2
bantime.maxtime = 604800 # Maximum 7 days
In practice with bantime = 3600 (1 hour) as the base: first offense gets 1 hour, second gets 2 hours, third gets 4 hours… Persistent offenders eventually get banned for an entire week.
Validating Your Configuration Before Reloading
# Check config syntax
sudo fail2ban-client --test
# Reload if no errors
sudo systemctl reload fail2ban
# Confirm which jails are active
sudo fail2ban-client status
Conclusion
Fail2ban doesn’t solve everything — it’s no substitute for using SSH keys instead of passwords, or keeping your packages updated. But within its role, it does the job well: minimal resource usage, automatic handling of most scanner bot noise, no manual intervention required.
A 15-minute setup in exchange for better sleep at night. After getting everything configured, I usually run sudo fail2ban-client status sshd every morning via cron to keep tabs on things. Watching that banned IP list grow longer every day is reassuring — the server is handling its own defense.

