Many system administrators face this common security dilemma: While temporary bans (like 300-second blocks after 3 failed attempts) help prevent brute-force attacks without locking out legitimate users who mistype credentials, we often need to identify and permanently block persistent attackers.
Fail2Ban's standard configuration (typically in /etc/fail2ban/jail.local
) might look like this:
[sshd] enabled = true port = ssh filter = sshd logpath = /var/log/auth.log maxretry = 3 findtime = 600 bantime = 300
This handles temporary blocking well, but lacks native functionality for permanent bans after repeated offenses.
We'll create a custom action that:
- Tracks how many times each IP gets banned
- Permanently blocks IPs reaching our threshold (5 offenses)
Step 1: Create the IP Tracking Script
Create /etc/fail2ban/action.d/ip_tracker.sh
:
#!/bin/bash TRACK_FILE="/etc/fail2ban/ip_offenders" THRESHOLD=5 IP=$1 # Create tracking file if not exists [ -f "$TRACK_FILE" ] || touch "$TRACK_FILE" # Get current count or initialize COUNT=$(grep -c "^$IP " "$TRACK_FILE" || echo 0) # Increment count ((COUNT++)) # Update record grep -v "^$IP " "$TRACK_FILE" > "$TRACK_FILE.tmp" echo "$IP $COUNT" >> "$TRACK_FILE.tmp" mv "$TRACK_FILE.tmp" "$TRACK_FILE" # Check if threshold reached if [ $COUNT -ge $THRESHOLD ]; then # Add to permanent block list iptables -A f2b-permanent -s $IP -j DROP # Remove from tracking grep -v "^$IP " "$TRACK_FILE" > "$TRACK_FILE.tmp" mv "$TRACK_FILE.tmp" "$TRACK_FILE" fi
Step 2: Create Permanent IPTables Chain
Add this to your firewall rules (before the Fail2Ban chain):
iptables -N f2b-permanent iptables -I INPUT -j f2b-permanent
Step 3: Configure Custom Action in Fail2Ban
Create /etc/fail2ban/action.d/permanent.conf
:
[Definition] actionstart = actionstop = actioncheck = actionban = /etc/fail2ban/action.d/ip_tracker.shactionunban =
Then update your jail configuration:
[sshd] enabled = true port = ssh filter = sshd logpath = /var/log/auth.log maxretry = 3 findtime = 600 bantime = 300 action = %(action_)s permanent
The permanent block list will persist through reboots if you save your iptables rules. To manage blocked IPs:
- View permanently blocked IPs:
iptables -L f2b-permanent -n
- Remove a permanent block:
iptables -D f2b-permanent -s 1.2.3.4 -j DROP
Fail2Ban has a built-in recidive
jail that can detect repeat offenders, though it doesn't support permanent bans directly:
[recidive] enabled = true logpath = /var/log/fail2ban.log bantime = 86400 # 1 day findtime = 86400 # 24 hours maxretry = 5
Fail2Ban's default behavior of temporary bans works well for most scenarios, but sophisticated attackers often exploit the temporary nature of these blocks. When dealing with persistent brute-force attacks, we need a more robust solution that can:
- Maintain existing temporary ban functionality (3 attempts → 300s ban)
- Track repeat offenders across multiple ban cycles
- Escalate to permanent bans after specified recurrence (5 cycles in this case)
Fail2Ban actually includes built-in functionality for this exact scenario through its recidive
jail. Here's how to implement it:
# /etc/fail2ban/jail.local
[recidive]
enabled = true
logpath = /var/log/fail2ban.log
bantime = -1 # Permanent ban
findtime = 1d # Look back period for repeat offenses
maxretry = 5 # Number of temporary bans before permanent
For a complete CentOS implementation with SSH protection:
# Main SSH jail configuration
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/secure
maxretry = 3
bantime = 300
findtime = 3600
# Recidive jail for permanent bans
[recidive]
enabled = true
logpath = /var/log/fail2ban.log
banaction = iptables-allports
bantime = -1
findtime = 86400
maxretry = 5
After implementing, verify the configuration:
fail2ban-client status sshd
fail2ban-client status recidive
To test the permanent banning:
- Generate 3 failed SSH attempts from a test IP
- Wait for temporary ban (verify with
fail2ban-client status sshd
) - After ban expires, repeat the process 4 more times
- On the 5th cycle, check
iptables -L
for permanent ban
For more granular control, consider these additional parameters:
# Customizing recidive behavior
[recidive]
...
ignoreip = 127.0.0.1/8 ::1 # Whitelist localhost
banaction = %(banaction_allports)s
chain = FORWARD # For specific network setups
If you need more complex logic than recidive provides, here's a Python script alternative:
#!/usr/bin/env python3
import re
from collections import defaultdict
from datetime import datetime, timedelta
# Config
LOG_FILE = '/var/log/fail2ban.log'
BAN_THRESHOLD = 5
BAN_FILE = '/etc/fail2ban/ip.permanentban'
def analyze_fail2ban_log():
ip_counts = defaultdict(int)
cutoff = datetime.now() - timedelta(days=1)
with open(LOG_FILE) as f:
for line in f:
if 'Ban' in line:
match = re.search(r'Ban (\d+\.\d+\.\d+\.\d+)', line)
if match:
ip = match.group(1)
ip_counts[ip] += 1
return [ip for ip, count in ip_counts.items()
if count >= BAN_THRESHOLD]
if __name__ == '__main__':
repeat_offenders = analyze_fail2ban_log()
with open(BAN_FILE, 'w') as f:
for ip in repeat_offenders:
f.write(f"{ip}\n")
# Then configure fail2ban to read this file