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:

  1. Log files — the source of truth. Services write authentication failures and errors to log files
  2. Filters — regular expressions that match specific patterns in log files (e.g., “Failed password for” in auth.log)
  3. Jails — configuration units that combine a filter with a log file path, thresholds (maxretry, findtime), and an action
  4. 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:

FilePurposeEditable?
/etc/fail2ban/fail2ban.confCore daemon settings (log level, socket)No — use fail2ban.local
/etc/fail2ban/jail.confDefault jail definitionsNo — use jail.local
/etc/fail2ban/jail.localYour custom jail overridesYes — this is your main config
/etc/fail2ban/jail.d/*.confAdditional jail configurationsYes
/etc/fail2ban/filter.d/*.confFilter definitions (regex patterns)Yes — for custom filters
/etc/fail2ban/action.d/*.confAction definitions (ban/unban commands)Yes — for custom actions

Important: Never edit jail.conf or fail2ban.conf directly. These files are overwritten during package updates. Always create .local files 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:

ActionBehavior
%(action_)sBan only (no notification)
%(action_mw)sBan + send email with WHOIS info
%(action_mwl)sBan + 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

CommandDescription
sudo systemctl start fail2banStart the Fail2Ban service
sudo systemctl stop fail2banStop the Fail2Ban service
sudo systemctl restart fail2banRestart the Fail2Ban service
sudo systemctl status fail2banCheck the service status
sudo fail2ban-client statusList all active jails
sudo fail2ban-client status sshdShow status and banned IPs for the sshd jail
sudo fail2ban-client set sshd banip 203.0.113.100Manually ban an IP in the sshd jail
sudo fail2ban-client set sshd unbanip 203.0.113.100Unban a specific IP from the sshd jail
sudo fail2ban-client unban --allUnban all IPs across all jails
sudo fail2ban-client get sshd bantimeShow the current ban duration for a jail
sudo fail2ban-client get sshd maxretryShow the current retry limit for a jail
sudo fail2ban-client get sshd ignoreipShow whitelisted IPs for a jail
sudo fail2ban-client reloadReload configuration without restarting
sudo fail2ban-client reload sshdReload a specific jail
sudo tail -100 /var/log/fail2ban.logView 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.log when 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:

  1. Unban them immediately:
sudo fail2ban-client set sshd unbanip USER_IP
  1. Add their IP or network to the whitelist:
sudo fail2ban-client set sshd addignoreip USER_IP
  1. For a permanent fix, add the IP to ignoreip in jail.local and 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 dbpurgeage to 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.local file instead of editing jail.conf directly
  • 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-regex before 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.