Set up Fail2ban intrusion prevention system with custom SSH, web server, and application protection rules. Configure email notifications and advanced filtering to automatically block malicious IP addresses based on log patterns.
Prerequisites
- Root or sudo access
- Active SSH service
- Basic understanding of log files
- Email server or SMTP access for notifications
What this solves
Fail2ban protects your server from brute force attacks and malicious access attempts by monitoring log files and automatically blocking suspicious IP addresses. This tutorial covers advanced configurations including custom jails for SSH, web servers, email alerts, and whitelist management for production environments.
Step-by-step installation
Update system packages
Start by updating your package manager to ensure you get the latest security patches and dependencies.
sudo apt update && sudo apt upgrade -y
Install Fail2ban and required dependencies
Install Fail2ban along with mail utilities for email notifications and additional filtering tools.
sudo apt install -y fail2ban sendmail iptables-persistent whois
Create main configuration file
Create a local configuration file to override default settings. Never edit the main jail.conf file directly as updates will overwrite your changes.
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
Configure basic Fail2ban settings
Edit the main configuration file with your preferred settings for ban times, retry attempts, and email notifications.
[DEFAULT]
Ban hosts for 24 hours (86400 seconds)
bantime = 86400
Number of failures before a host gets banned
maxretry = 5
Time frame in which failures are counted (10 minutes)
findtime = 600
Backend for storing ban information
backend = systemd
Destination email for notifications
destemail = admin@example.com
Sender email address
sender = fail2ban@example.com
Email subject line
subject = [Fail2ban] %(name)s: banned %(ip)s from %(fq-hostname)s
Enable email notifications
action = %(action_mwl)s
Whitelist your IP addresses (replace with your actual IPs)
ignoreip = 127.0.0.1/8 ::1 203.0.113.10/32
Custom ban action with detailed logging
banaction = iptables-multiport
banaction_allports = iptables-allports
Configure SSH protection jail
Add enhanced SSH protection with custom settings for port and log monitoring. This jail monitors both password and key-based authentication failures.
[sshd]
enabled = true
port = 22
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
findtime = 300
Advanced SSH filter with additional patterns
[sshd-aggressive]
enabled = true
port = 22
filter = sshd-aggressive
logpath = /var/log/auth.log
maxretry = 2
bantime = 86400
findtime = 600
Create custom SSH filter
Create an aggressive SSH filter that catches more attack patterns including connection drops and invalid users.
[Definition]
Aggressive SSH filter that catches multiple attack patterns
failregex = ^%(__prefix_line)s(?:error: PAM: )?[aA]uthentication (?:failure|error|failed) for . from ( via \S+)?\s $
^%(__prefix_line)s(?:error: )?Received disconnect from : 3: .*: Auth fail$
^%(__prefix_line)sFailed (?:password|publickey) for . from (?: port \d )?(?: ssh\d*)?$
^%(__prefix_line)sROOT LOGIN REFUSED. FROM \s $
^%(__prefix_line)siI user . from \s $
^%(__prefix_line)sUser .+ from not allowed because not listed in AllowUsers\s*$
^%(__prefix_line)srefused connect from \S+ \(\)\s*$
^%(__prefix_line)sConnection closed by .\[preauth\]\s$
^%(__prefix_line)sConnection from .on unused port\s$
ignoreregex =
[Init]
Read common prefixes
journalmatch = _SYSTEMD_UNIT=sshd.service + _COMM=sshd
Configure web server protection
Add protection for Nginx and Apache web servers against common attacks like 404 flooding and authentication failures.
# Nginx protection
[nginx-http-auth]
enabled = true
filter = nginx-http-auth
logpath = /var/log/nginx/error.log
maxretry = 3
bantime = 3600
[nginx-noscript]
enabled = true
filter = nginx-noscript
logpath = /var/log/nginx/access.log
maxretry = 6
bantime = 86400
[nginx-badbots]
enabled = true
filter = nginx-badbots
logpath = /var/log/nginx/access.log
maxretry = 2
bantime = 86400
[nginx-noproxy]
enabled = true
filter = nginx-noproxy
logpath = /var/log/nginx/access.log
maxretry = 2
bantime = 86400
Apache protection
[apache-auth]
enabled = true
filter = apache-auth
logpath = /var/log/apache2/error.log
maxretry = 3
bantime = 3600
[apache-badbots]
enabled = true
filter = apache-badbots
logpath = /var/log/apache2/access.log
maxretry = 2
bantime = 86400
[apache-noscript]
enabled = true
filter = apache-noscript
logpath = /var/log/apache2/access.log
maxretry = 6
bantime = 86400
Create custom web attack filter
Create a filter to detect common web application attacks including SQL injection attempts and directory traversal.
[Definition]
Custom filter for detecting web application attacks
failregex = ^ -.\"(GET|POST|PUT|DELETE).\\x.\" (4[0-9][0-9]|5[0-9][0-9]).$
^ -.\"(GET|POST).(union|select|insert|drop|delete|update|script|alert).\" (4[0-9][0-9]|5[0-9][0-9]).$
^ -.\"(GET|POST).\\.\\./.\" (4[0-9][0-9]|5[0-9][0-9]).$
^ -.\"(GET|POST).(eval|exec|system|passthru).\" (4[0-9][0-9]|5[0-9][0-9]).$
^ -.\" 40[4-9] .$
ignoreregex =
Configure email notifications
Set up detailed email notifications with custom templates that include attack information and geographic data.
[Definition]
Custom email action with enhanced information
actionstart = printf %%b "Hi,\n
The jail has been started successfully.\n
Regards,\n
Fail2Ban" | mail -s "[Fail2Ban] : started on hostname"
actionstop = printf %%b "Hi,\n
The jail has been stopped.\n
Regards,\n
Fail2Ban" | mail -s "[Fail2Ban] : stopped on hostname"
actioncheck =
actionban = printf %%b "Hi,\n
The IP has just been banned by Fail2Ban after
attempts against on hostname.\n\n
Lines containing IP: in \n
grep '' | tail -20 \n\n
WHOIS information for :\n
whois | head -20 \n\n
Regards,\n
Fail2Ban" | mail -s "[Fail2Ban] : banned from hostname"
actionunban = printf %%b "Hi,\n
The IP has just been unbanned by Fail2Ban.\n\n
Regards,\n
Fail2Ban" | mail -s "[Fail2Ban] : unbanned from hostname"
[Init]
name = default
dest = root
logpath = /var/log/fail2ban.log
Enable and start Fail2ban service
Start the Fail2ban service and enable it to start automatically on system boot.
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
sudo systemctl status fail2ban
Configure firewall persistence
Ensure that Fail2ban rules persist across reboots and integrate properly with your firewall configuration.
sudo systemctl enable netfilter-persistent
sudo netfilter-persistent save
Advanced configuration and monitoring
Set up log rotation
Configure log rotation for Fail2ban to prevent log files from consuming excessive disk space.
/var/log/fail2ban.log {
daily
missingok
rotate 30
compress
delaycompress
postrotate
/usr/bin/fail2ban-client flushlogs >/dev/null 2>&1 || true
endscript
create 640 root adm
}
Create monitoring script
Create a monitoring script to track Fail2ban performance and generate daily reports.
#!/bin/bash
Fail2ban daily report script
echo "=== Fail2ban Daily Report - $(date) ==="
echo ""
echo "Active Jails:"
fail2ban-client status
echo ""
for jail in $(fail2ban-client status | grep "Jail list:" | cut -d: -f2 | sed 's/,//g'); do
echo "=== $jail jail status ==="
fail2ban-client status $jail
echo ""
done
echo "Recent ban activity (last 24 hours):"
grep "Ban " /var/log/fail2ban.log | grep "$(date --date='1 day ago' '+%Y-%m-%d')\|$(date '+%Y-%m-%d')"
echo ""
echo "Top 10 banned IPs:"
grep "Ban " /var/log/fail2ban.log | awk '{print $7}' | sort | uniq -c | sort -nr | head -10
Make monitoring script executable
Set correct permissions for the monitoring script and add it to daily cron jobs.
sudo chmod 755 /usr/local/bin/fail2ban-report.sh
sudo ln -s /usr/local/bin/fail2ban-report.sh /etc/cron.daily/fail2ban-report
Testing and validation
Test SSH jail functionality
Test your SSH jail by attempting failed logins from another system or using a test account.
# Check if fail2ban is monitoring SSH
sudo fail2ban-client status sshd
Monitor live logs during testing
sudo tail -f /var/log/fail2ban.log
Test email notifications
Send a test email notification to verify your email configuration is working.
# Test mail system
echo "Test email from fail2ban" | mail -s "Test Email" admin@example.com
Check mail logs
sudo tail -f /var/log/mail.log
Verify your setup
# Check Fail2ban service status
sudo systemctl status fail2ban
List all active jails
sudo fail2ban-client status
Check specific jail status
sudo fail2ban-client status sshd
View recent Fail2ban activity
sudo tail -20 /var/log/fail2ban.log
Check current iptables rules created by Fail2ban
sudo iptables -L -n | grep fail2ban
Test configuration syntax
sudo fail2ban-client -t
Performance optimization
Optimize log processing
Configure Fail2ban to process logs more efficiently for high-traffic servers by adjusting polling intervals and backend settings.
[DEFAULT]
Optimize for high-traffic servers
backend = systemd
Reduce polling interval for faster detection
logencoding = auto
Use systemd journal for better performance
journalmatch = _SYSTEMD_UNIT=sshd.service
Configure database backend
Use SQLite database backend for better performance with large numbers of IPs and persistent storage.
[DEFAULT]
Use database backend for better performance
dbfile = /var/lib/fail2ban/fail2ban.sqlite3
dbpurgeage = 1d
Managing whitelists and permanent bans
Manage IP whitelist
Add trusted IP addresses to prevent them from being banned, including office networks and monitoring systems.
# Add IP to whitelist permanently (edit jail.local)
sudo nano /etc/fail2ban/jail.local
Add to ignoreip line: 203.0.113.0/24
Unban specific IP immediately
sudo fail2ban-client set sshd unbanip 203.0.113.50
Reload Fail2ban configuration
sudo fail2ban-client reload
Create permanent ban list
Set up a permanent ban list for repeat offenders and known malicious IP ranges.
# Permanent IP blacklist - one IP per line
Known attackers
198.51.100.5
198.51.100.0/24
Tor exit nodes (example)
203.0.113.100
203.0.113.101
Apply permanent blacklist
Create a jail that monitors the blacklist file and permanently bans listed IPs.
[blacklist]
enabled = true
filter = blacklist
action = iptables-allports[name=blacklist]
logpath = /etc/fail2ban/ip.blacklist
maxretry = 1
bantime = -1
findtime = 31536000
Integration with monitoring systems
For comprehensive server monitoring, consider integrating Fail2ban logs with Grafana and Prometheus monitoring setup. You can also secure your web applications further by implementing NGINX with modern security headers alongside Fail2ban protection.
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Fail2ban won't start | Configuration syntax error | sudo fail2ban-client -t to check config syntax |
| Email notifications not working | Mail system not configured | Install and configure sendmail: sudo apt install sendmail |
| Rules not blocking IPs | Wrong log file path or format | Check log paths in jail config match actual log locations |
| Self-lockout from SSH | IP not in whitelist | Add your IP to ignoreip in jail.local and reboot server |
| High CPU usage | Too frequent log polling | Increase findtime and reduce log polling frequency |
| Bans not persistent | Firewall rules not saved | Run sudo netfilter-persistent save on Ubuntu/Debian |
| Systemd journal errors | Wrong journalmatch filter | Verify service names: systemctl list-units | grep ssh |
Next steps
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Global variables
SCRIPT_NAME=$(basename "$0")
BACKUP_DIR="/root/fail2ban_backup_$(date +%Y%m%d_%H%M%S)"
# Usage function
usage() {
echo "Usage: $SCRIPT_NAME [OPTIONS]"
echo "Options:"
echo " -e EMAIL Admin email for notifications (required)"
echo " -i IP Additional IP to whitelist (optional, can be used multiple times)"
echo " -p PORT SSH port (default: 22)"
echo " -h Show this help message"
echo ""
echo "Example: $SCRIPT_NAME -e admin@example.com -i 192.168.1.100 -p 2222"
}
# Cleanup function for rollback
cleanup() {
echo -e "${RED}[ERROR] Installation failed. Rolling back changes...${NC}"
if [[ -d "$BACKUP_DIR" ]]; then
systemctl stop fail2ban 2>/dev/null || true
if [[ -f "$BACKUP_DIR/jail.local" ]]; then
cp "$BACKUP_DIR/jail.local" /etc/fail2ban/jail.local 2>/dev/null || true
fi
systemctl start fail2ban 2>/dev/null || true
echo -e "${YELLOW}Backup files available at: $BACKUP_DIR${NC}"
fi
exit 1
}
trap cleanup ERR
# Parse command line arguments
ADMIN_EMAIL=""
WHITELIST_IPS=()
SSH_PORT=22
while getopts "e:i:p:h" opt; do
case $opt in
e) ADMIN_EMAIL="$OPTARG" ;;
i) WHITELIST_IPS+=("$OPTARG") ;;
p) SSH_PORT="$OPTARG" ;;
h) usage; exit 0 ;;
*) usage; exit 1 ;;
esac
done
if [[ -z "$ADMIN_EMAIL" ]]; then
echo -e "${RED}Error: Admin email is required${NC}"
usage
exit 1
fi
# Check if running as root
if [[ $EUID -ne 0 ]]; then
echo -e "${RED}Error: This script must be run as root${NC}"
exit 1
fi
# Detect distribution
if [[ ! -f /etc/os-release ]]; then
echo -e "${RED}Error: Cannot detect distribution${NC}"
exit 1
fi
source /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update && apt upgrade -y"
PKG_INSTALL="apt install -y"
AUTH_LOG="/var/log/auth.log"
SENDMAIL_PKG="sendmail"
IPTABLES_PKG="iptables-persistent"
;;
almalinux|rocky|centos|rhel|ol)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
AUTH_LOG="/var/log/secure"
SENDMAIL_PKG="sendmail"
IPTABLES_PKG="iptables-services"
;;
fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
AUTH_LOG="/var/log/secure"
SENDMAIL_PKG="sendmail"
IPTABLES_PKG="iptables-services"
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum update -y"
PKG_INSTALL="yum install -y"
AUTH_LOG="/var/log/secure"
SENDMAIL_PKG="sendmail"
IPTABLES_PKG="iptables-services"
;;
*)
echo -e "${RED}Error: Unsupported distribution: $ID${NC}"
exit 1
;;
esac
echo -e "${GREEN}Starting Fail2ban installation for $PRETTY_NAME${NC}"
# Create backup directory
mkdir -p "$BACKUP_DIR"
echo -e "${YELLOW}[1/7] Updating system packages...${NC}"
$PKG_UPDATE
echo -e "${YELLOW}[2/7] Installing Fail2ban and dependencies...${NC}"
$PKG_INSTALL fail2ban $SENDMAIL_PKG $IPTABLES_PKG whois
# Backup existing configuration if it exists
if [[ -f /etc/fail2ban/jail.local ]]; then
cp /etc/fail2ban/jail.local "$BACKUP_DIR/"
fi
echo -e "${YELLOW}[3/7] Creating main configuration file...${NC}"
cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
echo -e "${YELLOW}[4/7] Configuring Fail2ban settings...${NC}"
# Build whitelist IP string
IGNORE_IPS="127.0.0.1/8 ::1"
for ip in "${WHITELIST_IPS[@]}"; do
IGNORE_IPS="$IGNORE_IPS $ip"
done
# Create main jail.local configuration
cat > /etc/fail2ban/jail.local << EOF
[DEFAULT]
# Ban hosts for 24 hours (86400 seconds)
bantime = 86400
# Number of failures before a host gets banned
maxretry = 5
# Time frame in which failures are counted (10 minutes)
findtime = 600
# Backend for storing ban information
backend = systemd
# Destination email for notifications
destemail = $ADMIN_EMAIL
# Sender email address
sender = fail2ban@$(hostname -f)
# Email subject line
subject = [Fail2ban] %(name)s: banned %(ip)s from %(fq-hostname)s
# Enable email notifications
action = %(action_mwl)s
# Whitelist IP addresses
ignoreip = $IGNORE_IPS
# Custom ban action with detailed logging
banaction = iptables-multiport
banaction_allports = iptables-allports
[sshd]
enabled = true
port = $SSH_PORT
filter = sshd
logpath = $AUTH_LOG
maxretry = 3
bantime = 3600
findtime = 300
[sshd-aggressive]
enabled = true
port = $SSH_PORT
filter = sshd-aggressive
logpath = $AUTH_LOG
maxretry = 2
bantime = 86400
findtime = 600
EOF
echo -e "${YELLOW}[5/7] Creating custom SSH filter...${NC}"
cat > /etc/fail2ban/filter.d/sshd-aggressive.conf << 'EOF'
[Definition]
# Aggressive SSH filter that catches multiple attack patterns
failregex = ^%(__prefix_line)s(?:error: PAM: )?[aA]uthentication (?:failure|error|failed) for .* from <HOST>( via \S+)?\s*$
^%(__prefix_line)s(?:error: )?Received disconnect from <HOST>: 3: .*: Auth fail$
^%(__prefix_line)sFailed (?:password|publickey) for .* from <HOST>(?: port \d*)?(?: ssh\d*)?$
^%(__prefix_line)sROOT LOGIN REFUSED .* FROM <HOST>\s*$
^%(__prefix_line)s[iI](?:llegal|nvalid) user .* from <HOST>\s*$
^%(__prefix_line)sUser .+ from <HOST> not allowed because not listed in AllowUsers\s*$
^%(__prefix_line)srefused connect from \S+ \(<HOST>\)\s*$
^%(__prefix_line)sConnection closed by <HOST> \[preauth\]\s*$
^%(__prefix_line)sConnection from <HOST> on unused port\s*$
ignoreregex =
[Init]
# Read common prefixes
journalmatch = _SYSTEMD_UNIT=sshd.service + _COMM=sshd
EOF
# Add web server protection if nginx or apache is detected
if command -v nginx >/dev/null 2>&1 && [[ -d /var/log/nginx ]]; then
echo -e "${YELLOW}Nginx detected, adding web server protection...${NC}"
cat >> /etc/fail2ban/jail.local << 'EOF'
[nginx-http-auth]
enabled = true
filter = nginx-http-auth
logpath = /var/log/nginx/error.log
maxretry = 3
bantime = 3600
[nginx-noscript]
enabled = true
filter = nginx-noscript
logpath = /var/log/nginx/access.log
maxretry = 6
bantime = 86400
[nginx-badbots]
enabled = true
filter = nginx-badbots
logpath = /var/log/nginx/access.log
maxretry = 2
bantime = 86400
EOF
fi
if command -v apache2 >/dev/null 2>&1 || command -v httpd >/dev/null 2>&1; then
echo -e "${YELLOW}Apache detected, adding web server protection...${NC}"
cat >> /etc/fail2ban/jail.local << 'EOF'
[apache-auth]
enabled = true
filter = apache-auth
logpath = /var/log/apache*/error.log
maxretry = 3
bantime = 3600
[apache-badbots]
enabled = true
filter = apache-badbots
logpath = /var/log/apache*/access.log
maxretry = 2
bantime = 86400
EOF
fi
echo -e "${YELLOW}[6/7] Setting proper permissions and starting services...${NC}"
# Set proper permissions
chmod 644 /etc/fail2ban/jail.local
chmod 644 /etc/fail2ban/filter.d/sshd-aggressive.conf
chown root:root /etc/fail2ban/jail.local
chown root:root /etc/fail2ban/filter.d/sshd-aggressive.conf
# Enable and start services
systemctl enable fail2ban
systemctl restart fail2ban
# Enable iptables service for RHEL-based systems
if [[ "$PKG_MGR" != "apt" ]]; then
systemctl enable iptables
systemctl start iptables
fi
echo -e "${YELLOW}[7/7] Verifying installation...${NC}"
# Verification checks
sleep 3
if ! systemctl is-active --quiet fail2ban; then
echo -e "${RED}Error: Fail2ban service is not running${NC}"
exit 1
fi
if ! fail2ban-client status >/dev/null 2>&1; then
echo -e "${RED}Error: Fail2ban client communication failed${NC}"
exit 1
fi
# Show status
echo -e "${GREEN}✓ Fail2ban installation completed successfully!${NC}"
echo ""
echo -e "${GREEN}Configuration Summary:${NC}"
echo "- Admin email: $ADMIN_EMAIL"
echo "- SSH port: $SSH_PORT"
echo "- Whitelisted IPs: $IGNORE_IPS"
echo "- Auth log: $AUTH_LOG"
echo ""
echo -e "${GREEN}Active jails:${NC}"
fail2ban-client status
echo ""
echo -e "${YELLOW}Useful commands:${NC}"
echo "- Check status: fail2ban-client status"
echo "- Check specific jail: fail2ban-client status sshd"
echo "- Unban IP: fail2ban-client set sshd unbanip IP_ADDRESS"
echo "- View logs: tail -f /var/log/fail2ban.log"
echo ""
echo -e "${GREEN}Installation complete! Your server is now protected by Fail2ban.${NC}"
Review the script before running. Execute with: bash install.sh