Skip to content

fail2ban Security Guide

Last Updated: 2026-02-09 Server: Hetzner Production (46.224.40.5) Status: ✅ 5 Jails Active | 45+ IPs Banned


fail2ban is our primary intrusion prevention system, protecting against brute-force attacks by monitoring logs and automatically banning malicious IPs.

┌─────────────────────────────────────────────────────────────┐
│ ATTACK FLOW │
├─────────────────────────────────────────────────────────────┤
│ Attacker → SSH/HTTP → Log Entry → fail2ban → iptables BAN │
└─────────────────────────────────────────────────────────────┘

Terminal window
# Status overview (run this first)
fail2ban-client status
# Check banned IPs in SSH jail
fail2ban-client status sshd
# Live attack monitoring (keep this running)
tail -f /var/log/fail2ban.log | grep --color -E "Ban|Found|Unban"
# Today's ban count
grep "Ban" /var/log/fail2ban.log | grep "$(date +%Y-%m-%d)" | wc -l
Terminal window
# Manually ban an IP immediately
fail2ban-client set sshd banip 192.168.1.100
# Unban an IP (use cautiously)
fail2ban-client set sshd unbanip 192.168.1.100
# Ban in nginx jails
fail2ban-client set nginx-botsearch banip 192.168.1.100
# Permanent ban via UFW (for persistent attackers)
ufw insert 1 deny from 192.168.1.100 comment "Permanent ban - brute force"

Current Configuration (Hardened 2026-02-09)

Section titled “Current Configuration (Hardened 2026-02-09)”
JailTargetMax RetryFind TimeBan Time
sshdSSH brute force21 hour1 week
recidiveRepeat offenders21 week30 days
nginx-http-authHTTP auth attacks310 min1 day
nginx-botsearchBot/scanner detection21 hour1 week
nginx-req-limitRequest flooding510 min1 day

Location: /etc/fail2ban/jail.local

# =============================================================================
# fail2ban Configuration - iSu Technologies
# Hardened SSH Protection (Updated 2026-02-09)
# =============================================================================
[DEFAULT]
bantime = 604800 # 1 week default
findtime = 3600 # 1 hour window
maxretry = 3
banaction = iptables-multiport
ignoreip = 127.0.0.1/8 46.224.40.5
# =============================================================================
# SSH Protection - Aggressive (1 week ban on 2 failures)
# =============================================================================
[sshd]
enabled = true
port = ssh
filter = sshd
backend = systemd
maxretry = 2 # Very strict - 2 failures
bantime = 604800 # 1 week ban
findtime = 3600 # 1 hour window
# =============================================================================
# Recidive - Repeat Offenders (30 day ban)
# =============================================================================
[recidive]
enabled = true
filter = recidive
logpath = /var/log/fail2ban.log
bantime = 2592000 # 30 days
findtime = 604800 # 1 week window
maxretry = 2 # After 2 bans
# =============================================================================
# Nginx Protection
# =============================================================================
[nginx-http-auth]
enabled = true
port = http,https
filter = nginx-http-auth
logpath = /var/log/nginx/error.log
maxretry = 3
bantime = 86400 # 1 day
findtime = 600 # 10 minutes
[nginx-botsearch]
enabled = true
port = http,https
filter = nginx-botsearch
logpath = /var/log/nginx/access.log
maxretry = 2
bantime = 604800 # 1 week
findtime = 3600 # 1 hour
[nginx-req-limit]
enabled = true
port = http,https
filter = nginx-limit-req
logpath = /var/log/nginx/error.log
maxretry = 5
bantime = 86400 # 1 day
findtime = 600 # 10 minutes

Terminal window
# 1. Check jail status
fail2ban-client status
# Expected output:
# Status
# |- Number of jail: 5
# `- Jail list: nginx-botsearch, nginx-http-auth, nginx-req-limit, recidive, sshd
# 2. Check SSH jail specifics
fail2ban-client status sshd
# 3. Check yesterday's activity
grep "$(date -d 'yesterday' +%Y-%m-%d)" /var/log/fail2ban.log | grep -c "Ban"
Terminal window
# Watch attacks live (recommended during incidents)
tail -f /var/log/fail2ban.log | grep --color -E "Ban|Found|Unban|WARNING"
# Watch specific jail
tail -f /var/log/fail2ban.log | grep sshd
# Count bans per IP (find persistent attackers)
grep "Ban" /var/log/fail2ban.log | awk '{print $NF}' | sort | uniq -c | sort -rn | head -20
Terminal window
# Total bans this week
grep "Ban" /var/log/fail2ban.log | grep -E "$(date -d '7 days ago' +%Y-%m-%d)|$(date -d '6 days ago' +%Y-%m-%d)|$(date -d '5 days ago' +%Y-%m-%d)|$(date -d '4 days ago' +%Y-%m-%d)|$(date -d '3 days ago' +%Y-%m-%d)|$(date -d '2 days ago' +%Y-%m-%d)|$(date -d '1 day ago' +%Y-%m-%d)|$(date +%Y-%m-%d)" | wc -l
# Top attackers (for permanent banning consideration)
grep "Ban" /var/log/fail2ban.log | awk '{print $NF}' | sort | uniq -c | sort -rn | head -10
# Check recidive catches
fail2ban-client status recidive

Terminal window
# Ban in SSH jail (most common)
fail2ban-client set sshd banip <IP>
# Ban in all nginx jails
fail2ban-client set nginx-http-auth banip <IP>
fail2ban-client set nginx-botsearch banip <IP>
fail2ban-client set nginx-req-limit banip <IP>
# Permanent ban (for persistent attackers - survives restart)
ufw insert 1 deny from <IP> comment "Permanent ban - reason here"
Terminal window
# Unban from specific jail
fail2ban-client set sshd unbanip <IP>
# Unban from all jails
fail2ban-client unban <IP>
# Check if IP is banned
fail2ban-client banned | grep <IP>
Terminal window
# View current whitelist
grep ignoreip /etc/fail2ban/jail.local
# Add IP to whitelist (edit jail.local)
# ignoreip = 127.0.0.1/8 46.224.40.5 YOUR_IP
# Reload after changes
systemctl restart fail2ban

Terminal window
# Check service status
systemctl status fail2ban
# Test configuration (before restart)
fail2ban-client -t
# View recent errors
journalctl -u fail2ban --no-pager -n 50
# Restart service
systemctl restart fail2ban
ProblemSolution
”No log file” errorCreate log file: touch /var/log/fail2ban.log && chmod 640 /var/log/fail2ban.log
Jail not startingCheck filter exists: ls /etc/fail2ban/filter.d/
Bans not workingVerify iptables chain: iptables -L f2b-sshd -n
Service won’t startCheck config: fail2ban-client -t

If log files are deleted (e.g., by attacker):

Terminal window
# Create fail2ban log
touch /var/log/fail2ban.log
chmod 640 /var/log/fail2ban.log
chown root:adm /var/log/fail2ban.log
# Create nginx logs
touch /var/log/nginx/access.log /var/log/nginx/error.log
chmod 640 /var/log/nginx/*.log
chown www-data:adm /var/log/nginx/*.log
# Restart services
systemctl restart fail2ban nginx

For IPs that repeatedly attack (caught by recidive multiple times), consider permanent UFW bans:

Terminal window
# Check top repeat offenders
grep "Ban" /var/log/fail2ban.log | awk '{print $NF}' | sort | uniq -c | sort -rn | head -10
# Permanent ban via UFW (example)
ufw insert 1 deny from 192.168.1.100 comment "Permanent ban - 50+ attacks"
# Verify permanent bans
ufw status | grep "DENY"

fail2ban works alongside our observability stack:

Featurefail2banPLGT Stack
Block attacks✅ Yes❌ No
Detect attacks✅ Yes✅ Yes (Loki)
Historical analysisLimited✅ Full (Grafana)
Multi-channel alertsBasic✅ Advanced
Dashboard visualization❌ No✅ Yes

Best Practice: Use both - fail2ban for active blocking, PLGT for deep analysis.


  • Service running? systemctl status fail2ban
  • All 5 jails active? fail2ban-client status
  • iptables chains exist? iptables -L | grep f2b
  • Log files present? ls -la /var/log/fail2ban.log
  • Configuration valid? fail2ban-client -t
  • Nginx logs accessible? ls -la /var/log/nginx/


Last hardening: 2026-02-09 Maintainer: Nhlanhla Mnyandu (nhlanhla@isutech.co.za)