Fail2ban is one of those tools I install on every server within the first 30 minutes. It’s not a silver bullet — a determined attacker with a botnet of millions of IPs won’t be slowed down much — but it handles opportunistic attacks effectively and keeps your auth logs from filling up with garbage.
The basic idea: Fail2ban reads log files, looks for patterns that indicate failed authentication attempts, and uses iptables (or nftables, or firewalld) to temporarily ban the offending IP.
Installation
# Debian/Ubuntu
apt install fail2ban
# CentOS/RHEL
dnf install fail2ban
The default installation includes a set of “jails” — configurations for various services. SSH is included and often enabled by default.
Configuration Approach
Never edit /etc/fail2ban/jail.conf directly — it gets overwritten on upgrades. Instead, create /etc/fail2ban/jail.local which overrides the defaults:
cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
Or create targeted override files in /etc/fail2ban/jail.d/:
touch /etc/fail2ban/jail.d/custom.conf
I prefer the jail.d approach — cleaner, easier to manage.
SSH Protection
Create /etc/fail2ban/jail.d/sshd.conf:
[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
backend = %(sshd_backend)s
maxretry = 3
findtime = 600
bantime = 3600
maxretry = 3 — ban after 3 failed attempts.findtime = 600 — count failures within a 10-minute window.bantime = 3600 — ban for 1 hour.
If you’re using a non-default SSH port:
port = 2222
Start and enable:
systemctl enable fail2ban
systemctl start fail2ban
Check SSH jail status:
fail2ban-client status sshd
Output shows: currently failed IPs, total failures, currently banned IPs, total bans.
Nginx Protection
Fail2ban can watch Nginx access logs too. For HTTP auth failures:
[nginx-http-auth]
enabled = true
filter = nginx-http-auth
port = http,https
logpath = /var/log/nginx/error.log
maxretry = 5
findtime = 300
bantime = 600
For blocking rapid 404 scanning (common for WordPress installations):
[nginx-404]
enabled = true
filter = nginx-404
port = http,https
logpath = /var/log/nginx/access.log
maxretry = 20
findtime = 60
bantime = 3600
You need a filter for nginx-404. Create /etc/fail2ban/filter.d/nginx-404.conf:
[Definition]
failregex = ^<HOST> .* "(GET|POST|HEAD) .* HTTP/.*" 404
ignoreregex =
Test your filter before relying on it:
fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/nginx-404.conf
This shows how many lines match the filter pattern. If it’s 0, something’s wrong with the regex or the log format.
Nginx Bad Bot Protection
I also block common bad bot User-Agents:
[nginx-badbots]
enabled = true
filter = nginx-badbots
port = http,https
logpath = /var/log/nginx/access.log
maxretry = 1
bantime = 86400
The nginx-badbots filter usually comes with Fail2ban. If not, create /etc/fail2ban/filter.d/nginx-badbots.conf with patterns matching known malicious bots.
Persistent Bans
By default, bans are in-memory and lost on Fail2ban restart. Enable persistent banning with a database:
In /etc/fail2ban/jail.d/custom.conf:
[DEFAULT]
dbfile = /var/lib/fail2ban/fail2ban.sqlite3
dbpurgeage = 86400 # Keep ban history for 24 hours
Or set bantime = -1 to make bans permanent until manually removed.
For repeat offenders, use bantime.increment:
[DEFAULT]
bantime.increment = true
bantime.factor = 1
bantime.formula = ban.Time * (1<<(ban.Count if ban.Count<20 else 20)) * banFactor
bantime.overalljails = false
This doubles the ban time for each subsequent offense. First ban: 1 hour, second: 2 hours, third: 4 hours, and so on.
Whitelisting IPs
Some IPs should never be banned — your office IP, monitoring servers, load balancers:
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 10.0.0.0/8 192.168.0.0/16 203.0.113.42
This is global. You can also set ignoreip per jail.
Be careful: if your deployment server has the same IP as where you administer from, whitelist it. Nothing like locking yourself out of a server because your CI/CD pipeline triggered too many requests.
Manual Ban and Unban
Sometimes you want to manually ban an IP:
fail2ban-client set sshd banip 1.2.3.4
Unban:
fail2ban-client set sshd unbanip 1.2.3.4
Check if a specific IP is currently banned:
fail2ban-client get sshd banlist | grep 1.2.3.4
Viewing Banned IPs
See all currently banned IPs across all jails:
fail2ban-client status
fail2ban-client status sshd
fail2ban-client status nginx-http-auth
Or check iptables directly to see the actual firewall rules Fail2ban created:
iptables -n -L f2b-sshd
Actions: What Happens When an IP Gets Banned
By default, Fail2ban adds iptables rules to drop traffic from banned IPs. The default action is iptables-multiport — it adds the IP to a chain and drops matching traffic.
Other actions are available:
iptables-allports— ban on all portsmail— send email notificationmail-whois— send email with whois info about the banned IP
Configure in jail:
[sshd]
enabled = true
action = iptables-multiport[name=sshd, port="ssh", protocol=tcp]
mail[name=sshd, dest=you@example.com, sender=fail2ban@server.com]
I send email notifications for SSH bans on important servers — a wave of SSH attempts can be an early indicator of a targeted attack.
Fail2ban with firewalld or nftables
On systems using firewalld (CentOS/RHEL default), configure accordingly:
[DEFAULT]
banaction = firewallcmd-ipset
banaction_allports = firewallcmd-allports
For nftables:
[DEFAULT]
banaction = nftables-multiport
Log Files to Watch
After running for a few hours:
tail -f /var/log/fail2ban.log
You’ll see entries like:
2024-01-15 03:42:11,000 fail2ban.filter[1234]: INFO [sshd] Found 1.2.3.4 - 2024-01-15 03:42:10
2024-01-15 03:42:15,000 fail2ban.actions[1234]: NOTICE [sshd] Ban 1.2.3.4
Watching this for a day on a fresh server is educational — you’ll see just how much automated scanning is hitting your ports constantly.
The combination of Fail2ban and properly configured SSH (key-only auth, sensible MaxAuthTries) makes brute-force attacks practically ineffective. As part of an overall hardening approach, the Linux server hardening checklist covers the full set of steps for securing a fresh server.