What Fail2Ban Does
Fail2Ban monitors log files for repeated authentication failures and temporarily bans the offending IP addresses using firewall rules. It is reactive, not preventive — it stops an attacker who is hammering a service after a configurable number of failed attempts.
It is not a substitute for proper access control: key-based SSH, strong passwords, network segmentation. But it is a practical layer that reduces noise and closes the window on credential stuffing attacks.
Installing Fail2Ban
On Ubuntu:
sudo apt update
sudo apt install fail2ban
Fail2Ban installs with a default configuration that protects SSH out of the box. Verify it is running:
sudo systemctl status fail2ban
Configuration Structure
Fail2Ban uses two configuration layers:
/etc/fail2ban/jail.conf— the defaults shipped with the package. Never edit this file directly. It is overwritten on package updates./etc/fail2ban/jail.local— your overrides. Create this file and put all your customisations here.
Create the override file:
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
Or, better, create a minimal jail.local that only contains what you change:
sudo nano /etc/fail2ban/jail.local
Global Defaults
Start with sensible global defaults in jail.local:
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
banaction = nftables-multiport
banaction_allports = nftables-allports
ignoreip = 127.0.0.1/8 ::1
- bantime — how long an IP stays banned. One hour is a reasonable starting point. For persistent attackers, increase to
24hor usebantime.increment(see below). - findtime — the window in which
maxretryfailures must occur. Five failures in ten minutes triggers a ban. - maxretry — the number of failures before banning.
- banaction — the firewall backend. Use
nftables-multiporton modern Ubuntu (22.04+). For older systems, useiptables-multiport. - ignoreip — never ban these addresses. Add your own management IP here.
Protecting SSH
The SSH jail is enabled by default. Harden it:
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 2h
Three failed SSH attempts in ten minutes earns a two-hour ban. If you run SSH on a non-standard port:
[sshd]
port = 2222
Aggressive Mode
For SSH, Fail2Ban has an aggressive filter that also catches connection attempts before authentication:
[sshd]
mode = aggressive
This catches port scanners that connect and disconnect without attempting a login.
Protecting Apache
Authentication Failures
[apache-auth]
enabled = true
port = http,https
filter = apache-auth
logpath = /var/log/apache2/error.log
maxretry = 5
bantime = 1h
Bad Bots and Scanners
[apache-badbots]
enabled = true
port = http,https
filter = apache-badbots
logpath = /var/log/apache2/access.log
maxretry = 2
bantime = 48h
Overflows and Exploits
[apache-overflows]
enabled = true
port = http,https
filter = apache-overflows
logpath = /var/log/apache2/error.log
maxretry = 2
bantime = 1h
PHP URL Injection
[php-url-fopen]
enabled = true
port = http,https
filter = php-url-fopen
logpath = /var/log/apache2/access.log
maxretry = 1
bantime = 48h
Progressive Banning
Fail2Ban supports incremental ban times. Repeat offenders get longer bans:
[DEFAULT]
bantime.increment = true
bantime.factor = 2
bantime.maxtime = 4w
bantime.overalljails = true
First ban: 1 hour. Second: 2 hours. Third: 4 hours. And so on, up to four weeks. The overalljails flag means an IP banned in the SSH jail counts toward escalation in the Apache jail too.
Writing Custom Filters
Fail2Ban filters live in /etc/fail2ban/filter.d/. Each filter defines a failregex — a regular expression that matches a log line indicating a failure.
Example: a custom filter for a web application that logs failed logins to /var/log/myapp/auth.log:
Create /etc/fail2ban/filter.d/myapp.conf:
[Definition]
failregex = ^<HOST> - - \[.*\] "POST /login HTTP/.*" 401
ignoreregex =
Then add the jail:
[myapp]
enabled = true
port = http,https
filter = myapp
logpath = /var/log/myapp/auth.log
maxretry = 5
bantime = 1h
Test your filter before enabling it:
sudo fail2ban-regex /var/log/myapp/auth.log /etc/fail2ban/filter.d/myapp.conf
This shows how many lines match without actually banning anything.
Managing Bans
Check Status
sudo fail2ban-client status
sudo fail2ban-client status sshd
The second command shows currently banned IPs and the total number of bans.
Unban an IP
sudo fail2ban-client set sshd unbanip 192.0.2.100
Ban an IP Manually
sudo fail2ban-client set sshd banip 192.0.2.100
Check the Firewall Rules
sudo nft list ruleset | grep fail2ban
Or with iptables:
sudo iptables -L f2b-sshd -n
Email Notifications
Fail2Ban can send email on ban events. Configure the action:
[DEFAULT]
destemail = admin@example.com
sender = fail2ban@example.com
mta = sendmail
action = %(action_mwl)s
The action_mwl action bans the IP, sends an email with whois data and the relevant log lines. Ensure your MTA is configured. For low-volume servers this is useful; for high-traffic servers the email volume can become overwhelming — use action_ (ban only) and review logs periodically instead.
Hardening Fail2Ban Itself
Use nftables
On Ubuntu 22.04+, prefer nftables over iptables:
[DEFAULT]
banaction = nftables-multiport
Set the Database Purge Age
Fail2Ban stores ban history in /var/lib/fail2ban/fail2ban.sqlite3. Purge old records:
[DEFAULT]
dbpurgeage = 7d
Restrict the Fail2Ban Socket
sudo chmod 0700 /var/run/fail2ban
Log Rotation
Ensure Fail2Ban's own logs rotate. Check /etc/logrotate.d/fail2ban exists and contains sensible settings. If not:
/var/log/fail2ban.log {
weekly
rotate 12
compress
delaycompress
missingok
notifempty
postrotate
fail2ban-client flushlogs >/dev/null 2>&1 || true
endscript
}
Testing Your Configuration
After editing jail.local, always test the configuration before restarting:
sudo fail2ban-client -t
If the test passes, restart:
sudo systemctl restart fail2ban
Then test a ban by deliberately failing SSH authentication from a test IP (not your management IP):
ssh -o PasswordAuthentication=yes baduser@your-server
Repeat until the ban triggers, then check:
sudo fail2ban-client status sshd
Summary
Install Fail2Ban, create jail.local with your overrides, enable jails for SSH and Apache at minimum, use progressive banning for repeat offenders, and test your filters before deploying them. It does not replace proper access control, but it is a practical, low-overhead layer that meaningfully reduces brute-force attack surface.