Last updated: March 22, 2026

Setting Up Fail2ban for Server Protection

A Linux server with SSH exposed to the internet will have thousands of brute-force attempts per day within hours of exposure. Fail2ban reads log files, detects repeated failures, and uses iptables/nftables to block the offending IP for a configurable time. This guide covers SSH protection, web server jails, email alerts, and custom jail creation.

Table of Contents

How Fail2ban Works

  1. A daemon monitors log files (e.g., /var/log/auth.log)
  2. When a regex pattern (filter) matches a configurable number of times within a time window
  3. Fail2ban fires an action: typically an iptables DROP rule for the source IP
  4. After the ban time expires, the rule is removed
Log file → Filter (regex) → Jail policy (maxretry, findtime, bantime) → Action (iptables ban)

Prerequisites

Before you begin, make sure you have the following ready:

Step 1 - Install Fail2ban

Debian/Ubuntu
sudo apt install fail2ban

RHEL/Fedora/CentOS
sudo dnf install fail2ban fail2ban-systemd

Arch Linux
sudo pacman -S fail2ban

Start and enable
sudo systemctl enable --now fail2ban

Check status
fail2ban-client status

Step 2 - Basic Configuration

Fail2ban’s configuration has two layers:

Create your configuration file
sudo nano /etc/fail2ban/jail.local
[DEFAULT]
Ban IPs for 1 hour by default
bantime = 3600

Look back 10 minutes for failures
findtime = 600

Allow 5 failures before banning
maxretry = 5

Your own IP. never ban this (comma-separated, CIDR OK)
ignoreip = 127.0.0.1/8 ::1 192.168.1.0/24 your.home.ip.here

Email alerts (requires mail configured)
destemail = admin@yourdomain.com
sender = fail2ban@yourdomain.com
mta = sendmail
action = %(action_mwl)s  # Mail with log excerpt

Use nftables instead of iptables (modern systems)
banaction = nftables
banaction_allports = nftables[type=allports]

Step 3 - SSH Jail

In /etc/fail2ban/jail.local, add:

[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
backend = %(sshd_backend)s
maxretry = 3
bantime = 86400   # 24 hours for SSH attackers
findtime = 300    # 5-minute window

Aggressive mode - ban for 1 week after repeat offenses
(requires fail2ban-extras or custom jail)
Reload fail2ban to apply
sudo fail2ban-client reload

Check sshd jail status
sudo fail2ban-client status sshd
Shows - Currently banned IPs, total banned, failures

Manually ban an IP
sudo fail2ban-client set sshd banip 1.2.3.4

Manually unban (e.g., if you locked yourself out)
sudo fail2ban-client set sshd unbanip 1.2.3.4

Step 4 - Nginx and Apache Jails

/etc/fail2ban/jail.local

[nginx-http-auth]
enabled = true
filter = nginx-http-auth
logpath = /var/log/nginx/error.log
maxretry = 5
bantime = 3600

[nginx-limit-req]
enabled = true
filter = nginx-limit-req
logpath = /var/log/nginx/error.log
maxretry = 10
findtime = 60
bantime = 600

[nginx-botsearch]
enabled = true
filter = nginx-botsearch
logpath = /var/log/nginx/access.log
maxretry = 2
bantime = 86400

[apache-auth]
enabled = true
filter = apache-auth
logpath = /var/log/apache2/error.log
maxretry = 5
bantime = 3600

[apache-overflows]
enabled = true
filter = apache-overflows
logpath = /var/log/apache2/error.log
maxretry = 2
bantime = 86400

Step 5 - Custom Jail and Filter

Create a jail for any service that logs authentication failures. Example: protecting a WordPress login:

Create a custom filter for WordPress XML-RPC attacks
sudo nano /etc/fail2ban/filter.d/wordpress-xmlrpc.conf
[Definition]
failregex = ^<HOST>.*POST.*/xmlrpc\.php HTTP.*
ignoreregex =
Add jail in jail.local:
[wordpress-xmlrpc]
enabled = true
filter = wordpress-xmlrpc
logpath = /var/log/nginx/access.log
maxretry = 5
bantime = 86400
findtime = 600
Test your filter against the log file before enabling
fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/wordpress-xmlrpc.conf
Shows - how many lines match, what was captured

Step 6 - Email Alerts

Configure email notifications for each ban event

Install sendmail or postfix (simplest for alerts)
sudo apt install sendmail

Test email action:
sudo nano /etc/fail2ban/jail.local
[DEFAULT]
destemail = you@yourdomain.com
sender = fail2ban@yourserver.com
mta = sendmail

action_mwl = mail with log excerpt (verbose)
action_mw  = mail only
action_    = ban only, no mail
action = %(action_mwl)s
Test that mail is working
echo "Test fail2ban alert" | mail -s "Test" you@yourdomain.com

Or use a Python-based mailer for more reliable delivery:
sudo nano /etc/fail2ban/action.d/mail-python.conf
#!/usr/bin/env python3
Custom mail action. saves to a file and sends via smtplib
import smtplib, sys
from email.message import EmailMessage

def send_alert(ip, jail, loglines):
    msg = EmailMessage()
    msg['Subject'] = f'Fail2ban: banned {ip} from {jail}'
    msg['From'] = 'fail2ban@yourserver.com'
    msg['To'] = 'admin@yourdomain.com'
    msg.set_content(f"IP {ip} was banned from jail {jail}\n\n{loglines}")

    with smtplib.SMTP('localhost') as s:
        s.send_message(msg)

if __name__ == '__main__':
    send_alert(sys.argv[1], sys.argv[2], sys.argv[3] if len(sys.argv) > 3 else "")

Step 7 - Persistent Bans (Database)

By default, fail2ban forgets bans on restart. Enable persistent bans via its database:

/etc/fail2ban/jail.local
[DEFAULT]
dbfile = /var/lib/fail2ban/fail2ban.sqlite3
dbpurgeage = 86400  # Keep ban history for 24 hours

Step 8 - Aggressive Mode: Increasing Ban Times for Repeat Offenders

Install fail2ban-extras for recidive jail (bans IPs that get banned repeatedly)
Add to jail.local:
[recidive]
enabled = true
logpath = /var/log/fail2ban.log
banaction = %(banaction_allports)s
bantime = 604800   # 1 week for repeat offenders
findtime = 86400   # 24-hour window
maxretry = 5       # 5 bans in 24 hours = 1 week ban

Step 9 - Monitor and Statistics

Overall status
sudo fail2ban-client status

Status of a specific jail
sudo fail2ban-client status sshd
sudo fail2ban-client status nginx-http-auth

List all currently banned IPs across all jails
sudo fail2ban-client status | grep "Jail list" | \
  sed 's/.*Jail list://;s/,/\n/g' | \
  while read j; do sudo fail2ban-client status $j 2>/dev/null | grep "Banned IP"; done

View fail2ban log
sudo tail -f /var/log/fail2ban.log

Count bans per jail (last 24 hours)
grep "Ban " /var/log/fail2ban.log | \
  awk '{print $6}' | sort | uniq -c | sort -rn | head -10

Top offending IPs
grep "Ban " /var/log/fail2ban.log | \
  awk '{print $NF}' | sort | uniq -c | sort -rn | head -10

Step 10 - Test Your Configuration

Deliberately trigger the SSH jail using a test (from a safe IP)
Use a secondary machine or ensure your IP is in ignoreip

Test fail2ban regex matching without blocking anything
fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf

Simulate a failure and check if it's detected
logger -t sshd "Failed password for invalid user testuser from 10.0.0.99 port 22222 ssh2"
Check if 10.0.0.99 appears as a failure:
fail2ban-client status sshd

Check your iptables/nftables rules after a real ban
sudo nft list ruleset | grep fail2ban
Or with iptables:
sudo iptables -L f2b-sshd -n --line-numbers

Troubleshooting

Configuration changes not taking effect

Restart the relevant service or application after making changes. Some settings require a full system reboot. Verify the configuration file path is correct and the syntax is valid.

Permission denied errors

Run the command with sudo for system-level operations, or check that your user account has the necessary permissions. On macOS, you may need to grant terminal access in System Settings > Privacy & Security.

Connection or network-related failures

Check your internet connection and firewall settings. If using a VPN, try disconnecting temporarily to isolate the issue. Verify that the target server or service is accessible from your network.

Related Articles

Frequently Asked Questions

How long does it take to complete this setup?

For a straightforward setup, expect 30 minutes to 2 hours depending on your familiarity with the tools involved. Complex configurations with custom requirements may take longer. Having your credentials and environment ready before starting saves significant time.

What are the most common mistakes to avoid?

The most frequent issues are skipping prerequisite steps, using outdated package versions, and not reading error messages carefully. Follow the steps in order, verify each one works before moving on, and check the official documentation if something behaves unexpectedly.

Do I need prior experience to follow this guide?

Basic familiarity with the relevant tools and command line is helpful but not strictly required. Each step is explained with context. If you get stuck, the official documentation for each tool covers fundamentals that may fill in knowledge gaps.

Can I adapt this for a different tech stack?

Yes, the underlying concepts transfer to other stacks, though the specific implementation details will differ. Look for equivalent libraries and patterns in your target stack. The architecture and workflow design remain similar even when the syntax changes.

Where can I get help if I run into issues?

Start with the official documentation for each tool mentioned. Stack Overflow and GitHub Issues are good next steps for specific error messages. Community forums and Discord servers for the relevant tools often have active members who can help with setup problems.