Fail2ban Advanced Configuration: Protect Nginx, WordPress, and APIs from Brute Force

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.

Fast • Reliable • Affordable VPS - DO It Now!

Get top VPS hosting with VPS.DO’s fast, low-cost plans. Try risk-free with our 7-day no-questions-asked refund and start today!