Fail2ban for Nginx & Web Apps
Extend Fail2ban to block brute force attacks on Nginx, WordPress login, phpMyAdmin, and custom web applications
The base Fail2ban setup covers SSH. This guide extends it to protect web applications, blocking IPs that repeatedly hit login pages, trigger 4xx errors, or attempt known exploit paths.
This guide assumes Fail2ban is already installed. If not, see the Fail2ban guide first.
How web jails work
Fail2ban reads Nginx log files, matches lines against regex filters, and bans IPs that exceed a threshold. Nginx must log the client IP, check that your log format includes $remote_addr.
Verify your log format in /etc/nginx/nginx.conf:
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" "$http_user_agent"';
access_log /var/log/nginx/access.log main;Jail 1: Block repeated 4xx errors
Bots scanning for vulnerabilities generate many 404s and 403s. Ban them:
sudo nano /etc/fail2ban/jail.local[nginx-http-auth]
enabled = true
port = http,https
filter = nginx-http-auth
logpath = /var/log/nginx/error.log
maxretry = 5
bantime = 3600
[nginx-botsearch]
enabled = true
port = http,https
filter = nginx-botsearch
logpath = /var/log/nginx/access.log
maxretry = 10
bantime = 86400
findtime = 60The nginx-botsearch filter is included with Fail2ban by default. Check it:
cat /etc/fail2ban/filter.d/nginx-botsearch.confJail 2: WordPress login brute force
Create a custom filter for wp-login.php:
sudo nano /etc/fail2ban/filter.d/wordpress.conf[Definition]
failregex = ^<HOST> .* "POST /wp-login.php
^<HOST> .* "POST /xmlrpc.php
ignoreregex =Add the jail:
[wordpress]
enabled = true
port = http,https
filter = wordpress
logpath = /var/log/nginx/access.log
maxretry = 5
bantime = 86400
findtime = 300Jail 3: phpMyAdmin brute force
sudo nano /etc/fail2ban/filter.d/phpmyadmin.conf[Definition]
failregex = ^<HOST> .* "POST /phpmyadmin/index.php.*
^<HOST> .* "POST /pma/index.php.*
ignoreregex =[phpmyadmin]
enabled = true
port = http,https
filter = phpmyadmin
logpath = /var/log/nginx/access.log
maxretry = 3
bantime = 86400
findtime = 60Jail 4: Nginx rate limit violations
When Nginx rate-limits a client, it logs limiting requests. Ban clients that get rate-limited repeatedly:
sudo nano /etc/fail2ban/filter.d/nginx-limit-req.conf[Definition]
failregex = limiting requests, excess:.* by zone.*client: <HOST>
ignoreregex =[nginx-limit-req]
enabled = true
port = http,https
filter = nginx-limit-req
logpath = /var/log/nginx/error.log
maxretry = 10
bantime = 3600
findtime = 60Jail 5: Generic bad bots (user agent filter)
Block well-known exploit scanners by user agent:
sudo nano /etc/fail2ban/filter.d/nginx-badbots.conf[Definition]
failregex = ^<HOST> .* "(GET|POST|HEAD).*HTTP.*" \d+ .* "(sqlmap|nikto|nessus|masscan|zgrab|nuclei|dirbuster|gobuster)"
ignoreregex =[nginx-badbots]
enabled = true
port = http,https
filter = nginx-badbots
logpath = /var/log/nginx/access.log
maxretry = 1
bantime = 604800
findtime = 60Apply all jails
sudo systemctl restart fail2ban
sudo fail2ban-client statusCheck a specific jail:
sudo fail2ban-client status wordpress
sudo fail2ban-client status nginx-botsearchTest a filter (before enabling)
Test if your regex matches real log lines:
sudo fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/wordpress.confOutput shows how many lines matched.
Whitelist your IP
Always whitelist your own IP to avoid locking yourself out:
In /etc/fail2ban/jail.local under [DEFAULT]:
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 YOUR_HOME_IP YOUR_OFFICE_IPUnban an IP
sudo fail2ban-client set wordpress unbanip 1.2.3.4Monitor bans in real time
sudo tail -f /var/log/fail2ban.log | grep -E "Ban|Unban"Full jail.local example
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1
bantime = 86400
findtime = 300
maxretry = 5
banaction = iptables-multiport
[sshd]
enabled = true
maxretry = 3
bantime = 86400
[nginx-http-auth]
enabled = true
logpath = /var/log/nginx/error.log
[nginx-botsearch]
enabled = true
logpath = /var/log/nginx/access.log
maxretry = 10
bantime = 86400
findtime = 60
[wordpress]
enabled = true
filter = wordpress
logpath = /var/log/nginx/access.log
maxretry = 5
bantime = 86400
[nginx-limit-req]
enabled = true
filter = nginx-limit-req
logpath = /var/log/nginx/error.log
maxretry = 10