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
- 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)
Prerequisites
Before you begin, make sure you have the following ready:
- A computer running macOS, Linux, or Windows
- Terminal or command-line access
- Administrator or sudo privileges (for system-level changes)
- A stable internet connection for downloading tools
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:
/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]
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
- SSH Server Hardening Guide
- How to Set Up Mullvad VPN on Linux
- Set Up Own Email Server For Maximum Privacy Using Mail
- Setting Up ClamAV Antivirus on Linux
- SSH Server Hardening Config Guide
- AI Coding Assistant Session Data Lifecycle 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.