Security

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 = 60

The nginx-botsearch filter is included with Fail2ban by default. Check it:

cat /etc/fail2ban/filter.d/nginx-botsearch.conf

Jail 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 = 300

Jail 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 = 60

Jail 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 = 60

Jail 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 = 60

Apply all jails

sudo systemctl restart fail2ban
sudo fail2ban-client status

Check a specific jail:

sudo fail2ban-client status wordpress
sudo fail2ban-client status nginx-botsearch

Test 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.conf

Output 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_IP

Unban an IP

sudo fail2ban-client set wordpress unbanip 1.2.3.4

Monitor 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

On this page