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.
How Fail2ban Works
- A daemon monitors log files (e.g.,
/var/log/auth.log) - When a regex pattern (filter) matches a configurable number of times within a time window
- Fail2ban fires an action: typically an iptables DROP rule for the source IP
- After the ban time expires, the rule is removed
Log file → Filter (regex) → Jail policy (maxretry, findtime, bantime) → Action (iptables ban)
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
Basic Configuration
Fail2ban’s configuration has two layers:
/etc/fail2ban/jail.conf— default settings (don’t edit this, it gets overwritten on updates)/etc/fail2ban/jail.local— your overrides (this persists across updates)
# 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]
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
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
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
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 "")
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
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
Monitoring 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
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
Related Reading
- Setting Up ClamAV Antivirus on Linux
- How to Set Up a Honeypot for Intrusion Detection
- Securing Redis and MongoDB for Production
Built by theluckystrike — More at zovo.one
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.