Every server connected to the internet is a target. Automated bots scan IP ranges around the clock, attempting thousands of username and password combinations against SSH, web login pages, and mail servers. A single unprotected service can be compromised within hours. Fail2Ban is an intrusion prevention framework that monitors log files for signs of malicious activity and automatically bans offending IP addresses using firewall rules. This guide walks you through installing, configuring, and tuning Fail2Ban on Ubuntu, covering SSH protection, custom jails for Nginx, integration with UFW, email notifications, and essential management commands.
Prerequisites
Before you begin, make sure you have:
- Ubuntu Server 20.04, 22.04, or 24.04
- Terminal access with sudo privileges
- SSH access to the server
- A firewall configured and active (UFW recommended — see our UFW guide)
- Basic familiarity with log files and systemd services
What Is Fail2Ban?
Fail2Ban is an open-source intrusion prevention tool written in Python. It runs as a background daemon that continuously monitors log files (such as /var/log/auth.log or /var/log/nginx/error.log) for patterns that indicate malicious behavior. When a pattern is matched a configurable number of times within a defined time window, Fail2Ban creates a firewall rule to block the offending IP address for a specified duration.
Key characteristics:
- Log-driven — it reacts to actual failed attempts recorded in log files, not just connection rates
- Configurable jails — each service (SSH, Nginx, Postfix) gets its own set of rules called a “jail”
- Multiple ban actions — can use iptables, UFW, firewalld, or even send notifications
- Automatic unbanning — banned IPs are automatically released after the ban period expires
- Lightweight — minimal resource usage even on busy servers
How Fail2Ban Works
Fail2Ban operates through four interconnected components:
- Log files — the source of truth. Services write authentication failures and errors to log files
- Filters — regular expressions that match specific patterns in log files (e.g., “Failed password for” in auth.log)
- Jails — configuration units that combine a filter with a log file path, thresholds (maxretry, findtime), and an action
- Actions — what happens when the threshold is exceeded (create a firewall rule, send an email, run a script)
The workflow is straightforward:
Log file entry → Filter matches pattern → Jail counter increments
→ Counter exceeds maxretry within findtime → Action executes (ban IP)
→ Bantime expires → Action reverses (unban IP)
This architecture makes Fail2Ban highly extensible. You can write custom filters for any application that produces log output.
Installing Fail2Ban
Install Fail2Ban from the Ubuntu repositories:
sudo apt update
sudo apt install fail2ban -y
Verify the installation:
fail2ban-client --version
Check the service status:
sudo systemctl status fail2ban
Fail2Ban starts automatically after installation, but it ships with a minimal default configuration. The next step is to create a proper configuration.
Understanding the Configuration Files
Fail2Ban uses a layered configuration system:
| File | Purpose | Editable? |
|---|---|---|
/etc/fail2ban/fail2ban.conf | Core daemon settings (log level, socket) | No — use fail2ban.local |
/etc/fail2ban/jail.conf | Default jail definitions | No — use jail.local |
/etc/fail2ban/jail.local | Your custom jail overrides | Yes — this is your main config |
/etc/fail2ban/jail.d/*.conf | Additional jail configurations | Yes |
/etc/fail2ban/filter.d/*.conf | Filter definitions (regex patterns) | Yes — for custom filters |
/etc/fail2ban/action.d/*.conf | Action definitions (ban/unban commands) | Yes — for custom actions |
Important: Never edit
jail.conforfail2ban.confdirectly. These files are overwritten during package updates. Always create.localfiles that override specific settings.
Create your local configuration:
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
Alternatively, create a minimal jail.local from scratch with only the settings you want to override:
sudo nano /etc/fail2ban/jail.local
Configuring SSH Protection
SSH protection is the most common and critical use case for Fail2Ban. Add the following to your jail.local:
[DEFAULT]
# Ban duration: 1 hour
bantime = 3600
# Time window to count failures: 10 minutes
findtime = 600
# Number of failures before ban
maxretry = 3
# Use UFW for ban actions (recommended on Ubuntu)
banaction = ufw
# Your IP addresses (space-separated) to never ban
ignoreip = 127.0.0.1/8 ::1 YOUR_HOME_IP
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 86400
findtime = 600
This configuration:
- Sets a global default of 3 retries within 10 minutes, resulting in a 1-hour ban
- Overrides the SSH jail to ban for 24 hours (86400 seconds) instead
- Uses UFW as the ban mechanism
- Whitelists localhost and your home IP
If you run SSH on a non-standard port (recommended — see our SSH hardening guide), update the port setting:
[sshd]
enabled = true
port = 2222
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 86400
Restart Fail2Ban to apply:
sudo systemctl restart fail2ban
Verify the SSH jail is active:
sudo fail2ban-client status sshd
Expected output:
Status for the jail: sshd
|- Filter
| |- Currently failed: 0
| |- Total failed: 0
| `- File list: /var/log/auth.log
`- Actions
|- Currently banned: 0
|- Total banned: 0
`- Banned IP list:
Custom Jail Configuration
Beyond SSH, you can create jails for virtually any service. Here are practical examples:
Aggressive SSH Protection
For servers under heavy attack, use progressive banning with bantime.increment:
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
findtime = 600
bantime = 3600
bantime.increment = true
bantime.factor = 2
bantime.maxtime = 604800
With this configuration, the first ban lasts 1 hour, the second ban lasts 2 hours, the third lasts 4 hours, and so on, up to a maximum of 7 days (604800 seconds). Repeat offenders are penalized more heavily each time.
Recidive Jail (Ban Repeat Offenders Across All Jails)
The recidive jail monitors the Fail2Ban log itself and bans IPs that get banned repeatedly across any jail:
[recidive]
enabled = true
filter = recidive
logpath = /var/log/fail2ban.log
bantime = 604800
findtime = 86400
maxretry = 3
banaction = ufw
This bans an IP for 7 days if it gets banned 3 times within 24 hours from any jail.
Integration with UFW
For Ubuntu servers using UFW as the firewall, configure Fail2Ban to use UFW for banning:
[DEFAULT]
banaction = ufw
Verify the integration by checking UFW rules after a ban:
sudo ufw status numbered
Banned IPs appear as deny rules:
[ 1] Anywhere DENY IN 203.0.113.100
[ 2] 22/tcp ALLOW IN Anywhere
If you prefer iptables (for example, on systems without UFW), use the default:
[DEFAULT]
banaction = iptables-multiport
To verify iptables bans:
sudo iptables -L f2b-sshd -n
Protecting Nginx
Fail2Ban can protect web servers from various attack patterns. Here are two common Nginx jails:
Nginx HTTP Authentication
Protect pages behind HTTP basic authentication from brute-force attacks:
[nginx-http-auth]
enabled = true
port = http,https
filter = nginx-http-auth
logpath = /var/log/nginx/error.log
maxretry = 3
bantime = 3600
findtime = 600
The built-in nginx-http-auth filter matches lines like:
no user/password was provided for basic authentication
Nginx 404 Flood Protection
Ban IPs that generate excessive 404 errors (a common pattern for vulnerability scanners):
Create a custom filter:
sudo nano /etc/fail2ban/filter.d/nginx-404.conf
[Definition]
failregex = ^<HOST> - .* "(GET|POST|HEAD).*HTTP.*" 404
ignoreregex =
Add the jail:
[nginx-404]
enabled = true
port = http,https
filter = nginx-404
logpath = /var/log/nginx/access.log
maxretry = 30
findtime = 600
bantime = 3600
This bans an IP after 30 404 errors within 10 minutes. Set the threshold high enough to avoid banning legitimate users who encounter broken links.
Nginx Bot and Scanner Protection
Create a filter for known malicious user agents and path scanning:
sudo nano /etc/fail2ban/filter.d/nginx-badbots.conf
[Definition]
failregex = ^<HOST> - .* "(GET|POST).*HTTP.*" .* ".*(?:sqlmap|nikto|nmap|masscan|ZmEu|w3af).*"
ignoreregex =
[nginx-badbots]
enabled = true
port = http,https
filter = nginx-badbots
logpath = /var/log/nginx/access.log
maxretry = 1
findtime = 86400
bantime = 604800
Email Notifications
Configure Fail2Ban to send email alerts when IPs are banned or jails start/stop. First, install a mail transfer agent:
sudo apt install sendmail -y
Add notification settings to jail.local:
[DEFAULT]
destemail = admin@yourdomain.com
sender = fail2ban@yourdomain.com
mta = sendmail
action = %(action_mwl)s
The available action shortcuts are:
| Action | Behavior |
|---|---|
%(action_)s | Ban only (no notification) |
%(action_mw)s | Ban + send email with WHOIS info |
%(action_mwl)s | Ban + send email with WHOIS info + relevant log lines |
For a specific jail, override the action:
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 86400
action = %(action_mwl)s
Note: On high-traffic servers under constant attack, email notifications can generate a large volume of messages. Consider using
%(action_)s(ban only) for busy jails and reserving email notifications for critical jails or the recidive jail.
Whitelisting IPs
Prevent accidental bans on trusted IP addresses using the ignoreip directive:
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 10.0.0.0/8 192.168.1.0/24 YOUR_PUBLIC_IP
You can list individual IPs, CIDR ranges, or hostnames (resolved at startup):
ignoreip = 127.0.0.1/8 ::1 office.yourdomain.com 203.0.113.50
To whitelist an IP for a specific jail only:
[sshd]
enabled = true
ignoreip = 127.0.0.1/8 ::1 10.0.1.50
You can also whitelist at runtime without restarting Fail2Ban:
# Check current whitelist
sudo fail2ban-client get sshd ignoreip
# Add an IP to the whitelist temporarily (until restart)
sudo fail2ban-client set sshd addignoreip 203.0.113.50
Essential Commands Reference
| Command | Description |
|---|---|
sudo systemctl start fail2ban | Start the Fail2Ban service |
sudo systemctl stop fail2ban | Stop the Fail2Ban service |
sudo systemctl restart fail2ban | Restart the Fail2Ban service |
sudo systemctl status fail2ban | Check the service status |
sudo fail2ban-client status | List all active jails |
sudo fail2ban-client status sshd | Show status and banned IPs for the sshd jail |
sudo fail2ban-client set sshd banip 203.0.113.100 | Manually ban an IP in the sshd jail |
sudo fail2ban-client set sshd unbanip 203.0.113.100 | Unban a specific IP from the sshd jail |
sudo fail2ban-client unban --all | Unban all IPs across all jails |
sudo fail2ban-client get sshd bantime | Show the current ban duration for a jail |
sudo fail2ban-client get sshd maxretry | Show the current retry limit for a jail |
sudo fail2ban-client get sshd ignoreip | Show whitelisted IPs for a jail |
sudo fail2ban-client reload | Reload configuration without restarting |
sudo fail2ban-client reload sshd | Reload a specific jail |
sudo tail -100 /var/log/fail2ban.log | View recent Fail2Ban log entries |
sudo zgrep "Ban" /var/log/fail2ban.log* | Search all log files for ban events |
Troubleshooting
Fail2Ban Does Not Start
Check the logs for configuration errors:
sudo fail2ban-client -t
sudo journalctl -u fail2ban --since "10 minutes ago"
Common causes:
- Syntax errors in
jail.local(missing brackets, incorrect indentation) - Referenced log files that do not exist (e.g.,
/var/log/nginx/error.logwhen Nginx is not installed) - Filter regex errors in custom filter files
IPs Are Not Being Banned
Verify the jail is enabled and monitoring the correct log file:
sudo fail2ban-client status sshd
Check that the filter matches entries in the log:
sudo fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf
This command tests the filter against the actual log file and reports how many matches it finds. If the match count is zero, the filter is not detecting the expected patterns.
Legitimate Users Getting Banned
If users report being locked out:
- Unban them immediately:
sudo fail2ban-client set sshd unbanip USER_IP
- Add their IP or network to the whitelist:
sudo fail2ban-client set sshd addignoreip USER_IP
- For a permanent fix, add the IP to
ignoreipinjail.localand reload.
Fail2Ban Bans Are Not Persisted After Restart
By default, Fail2Ban does not persist bans across service restarts. To enable persistence, configure a ban database:
[DEFAULT]
dbfile = /var/lib/fail2ban/fail2ban.sqlite3
dbpurgeage = 86400
The dbpurgeage setting controls how long (in seconds) old entries are kept in the database.
High Memory Usage with Many Jails
If Fail2Ban uses excessive memory:
- Reduce the number of active jails to only what you need
- Set
dbpurgeageto a lower value to reduce database size - Ensure log files are being rotated properly with logrotate
Summary
Fail2Ban is an essential security tool for any internet-facing Linux server. By monitoring log files and automatically banning IPs that exhibit malicious behavior, it provides a proactive defense layer that works alongside your firewall. The key configurations covered in this guide — SSH protection, Nginx jails, UFW integration, and email notifications — address the most common attack vectors for production servers.
Key takeaways:
- Always create a
jail.localfile instead of editingjail.confdirectly - Whitelist your own IP addresses to prevent accidental lockout
- Use progressive ban times to penalize repeat offenders
- Combine Fail2Ban with UFW for consistent firewall management
- Test custom filters with
fail2ban-regexbefore deploying them - Monitor the Fail2Ban log regularly to understand your server’s threat landscape
For a comprehensive security setup, combine Fail2Ban with the techniques described in our guides on SSH Hardening, UFW Firewall Configuration, and the Linux Server Security Checklist.