Fail2ban Advanced Configuration: Protect Nginx, WordPress, and APIs from Brute Force
Fail2ban’s default configuration only protects SSH. But Nginx, WordPress login endpoints, and APIs also face constant brute-force attempts, credential stuffing, and vulnerability scanning. This guide creates custom Fail2ban jails that detect and ban malicious traffic at the Nginx/UFW level — before requests even reach PHP or your application.
How Fail2ban Works
<code">Log file → Fail2ban filter (regex) → Matches threshold → Action (ban IP via UFW/iptables) # Example flow: # Nginx access.log shows: 198.51.100.1 - - [01/Jun] "POST /wp-login.php" 403 # Fail2ban counts: 5 failures from 198.51.100.1 in 10 minutes # Action: sudo ufw insert 1 deny from 198.51.100.1 (ban for 24 hours)
Step 1: Install and Verify Fail2ban
<code">sudo apt install -y fail2ban sudo systemctl enable fail2ban # Test SSH jail is running sudo fail2ban-client status sudo fail2ban-client status sshd
Step 2: Main Configuration
<code">sudo nano /etc/fail2ban/jail.local
<code">[DEFAULT] # Ban time (seconds): 86400 = 24 hours, 604800 = 1 week bantime = 86400 # Window to count failures (seconds) findtime = 600 # 10 minutes # Number of failures before ban maxretry = 5 # Whitelist your own IP — IMPORTANT to avoid locking yourself out ignoreip = 127.0.0.1/8 ::1 YOUR_HOME_IP YOUR_OFFICE_IP # Action: ban via UFW (better than iptables on Ubuntu) banaction = ufw # Notification (configured in Step 5) action = %(action_mwl)s # ───────────────────────────────────────────── # SSH — tighter than default # ───────────────────────────────────────────── [sshd] enabled = true port = ssh logpath = %(sshd_log)s maxretry = 3 bantime = 604800 # 1 week for SSH brute force — be aggressive # ───────────────────────────────────────────── # Nginx — HTTP auth brute force # ───────────────────────────────────────────── [nginx-http-auth] enabled = true port = http,https logpath = /var/log/nginx/error.log maxretry = 5 # ───────────────────────────────────────────── # WordPress login brute force # ───────────────────────────────────────────── [wordpress-auth] enabled = true port = http,https logpath = /var/log/nginx/access.log filter = wordpress-auth maxretry = 5 bantime = 86400 findtime = 300 # ───────────────────────────────────────────── # Nginx 404 scanner / vulnerability scan # ───────────────────────────────────────────── [nginx-botsearch] enabled = true port = http,https logpath = /var/log/nginx/access.log filter = nginx-botsearch maxretry = 10 bantime = 86400 findtime = 60 # ───────────────────────────────────────────── # Recidive jail — extend ban for repeat offenders # ───────────────────────────────────────────── [recidive] enabled = true logpath = /var/log/fail2ban.log maxretry = 3 # Ban if jailed 3 times in findtime findtime = 86400 # Look back 24 hours bantime = 604800 # Ban for 1 week
Step 3: WordPress Login Filter
<code">sudo nano /etc/fail2ban/filter.d/wordpress-auth.conf
<code">[Definition]
# Match failed WordPress login attempts in Nginx access log
# WordPress returns 200 even on failed login, so match the POST to wp-login.php
failregex = ^ .* "POST /wp-login\.php.*" 200
^ .* "POST /wp-login\.php.*" 302
^ .* "POST /xmlrpc\.php.*" 200
ignoreregex =
# Test your filter against log file:
# sudo fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/wordpress-auth.conf
Step 4: Nginx Scanner/Bad Bot Filter
<code">sudo nano /etc/fail2ban/filter.d/nginx-botsearch.conf
<code">[Definition]
# Match repeated 404s (scanners probing for vulnerabilities)
# Match known bad paths
failregex = ^ .* "(GET|POST|HEAD) /(wp-content|phpmyadmin|admin|manager|.env|config|backup).*" (404|403)
^ .* "(GET|POST) /\.git.*" [0-9]+
^ .* ".*\.php.*" 404
ignoreregex =
Step 5: Custom API Rate Limit Jail
<code">sudo nano /etc/fail2ban/filter.d/api-ratelimit.conf
<code">[Definition] # Match Nginx 429 (Too Many Requests) — triggered by Nginx rate limiting failregex = ^ .* ".*" 429 ignoreregex =
<code"># Add to jail.local: [api-ratelimit] enabled = true port = http,https logpath = /var/log/nginx/access.log filter = api-ratelimit maxretry = 10 # After 10 x 429s in findtime bantime = 3600 # Ban for 1 hour findtime = 60
Step 6: Test Filters Before Enabling
<code"># Test a filter against your actual log file
sudo fail2ban-regex /var/log/nginx/access.log \
/etc/fail2ban/filter.d/wordpress-auth.conf \
--print-all-matched | tail -30
# Output shows matched lines and IPs — verify it's catching real attacks
# and NOT matching legitimate traffic
# Reload Fail2ban after configuration changes
sudo fail2ban-client reload
# Verify new jails are active
sudo fail2ban-client status
Step 7: Telegram Notifications for Bans
<code">sudo nano /etc/fail2ban/action.d/telegram-notify.conf
<code">[Definition]
actionstart =
actionstop =
actioncheck =
actionban = curl -s -X POST "https://api.telegram.org/bot/sendMessage" \
-d "chat_id=" \
-d "text=🚫 Fail2ban: Banned on jail (%(failures)s failures)" \
> /dev/null 2>&1
actionunban = curl -s -X POST "https://api.telegram.org/bot/sendMessage" \
-d "chat_id=" \
-d "text=✅ Fail2ban: Unbanned from jail" \
> /dev/null 2>&1
[Init]
BOT_TOKEN = your_telegram_bot_token
CHAT_ID = your_telegram_chat_id
<code"># Add to jail.local [DEFAULT] section:
action = ufw
telegram-notify[name=%(__name__)s]
Management Commands
<code"># View currently banned IPs in a jail sudo fail2ban-client status wordpress-auth sudo fail2ban-client status sshd # Manually ban an IP sudo fail2ban-client set sshd banip 198.51.100.1 # Manually unban an IP (e.g., if you accidentally blocked yourself) sudo fail2ban-client set sshd unbanip 198.51.100.1 # Unban from ALL jails at once sudo fail2ban-client unban 198.51.100.1 # View all currently banned IPs across all jails sudo fail2ban-client banned # View ban history in the log sudo grep -i "ban\|unban" /var/log/fail2ban.log | tail -50
Getting Started
Install and configure Fail2ban within the first hour of VPS provisioning. The jails in this guide run on any Ubuntu VPS at VPS.DO without additional packages. Add your home and office IP to ignoreip before enabling jails — accidentally banning your own IP requires VPS.DO’s emergency KVM console for recovery. Always test new filters with fail2ban-regex before enabling the jail.
Conclusion
Fail2ban extended to Nginx, WordPress, and APIs creates a dynamic IP blocklist that adapts to actual attack patterns on your server — banning IPs that attempt credential stuffing, vulnerability scanning, or rate abuse before they cause damage. The recidive jail permanently extends bans for persistent offenders, and Telegram notifications provide real-time visibility into active attacks. Combined with UFW’s static rules and SSH key authentication, Fail2ban completes a layered VPS security posture.