Secure NGINX with Custom Fail2ban Filters
You’ll get a working NGINX that actually blocks the obvious attacks and a Fail2Ban filter that knows what to look for. By the end of this you can throw away the default “allow‑everything” config, lock down the server, and see real‑time bans in your log.
Install and harden NGINX
- Grab the latest package – on Debian/Ubuntu run sudo apt update && sudo apt install nginx. On CentOS/RHEL use sudo yum install epel-release && sudo yum install nginx. Fresh packages include recent security patches, so you’re not chasing yesterday’s vulnerabilities.
- Turn off server tokens – edit /etc/nginx/nginx.conf and add server_tokens off; inside the main block. This removes the version number from error pages, denying attackers a quick clue about your stack.
- Enable only needed modules – if you compiled NGINX yourself, drop any --with-http_… flags you don’t use. For most sites you only need http_ssl_module and http_realip_module. Unused code is just extra attack surface.
- Force TLS 1.2+ – in your site’s SSL block set ssl_protocols TLSv1.2 TLSv1.3; and ssl_prefer_server_ciphers on;. Older protocols are riddled with known exploits, so this alone blocks a whole class of opportunistic scanners.
- Limit request size – add client_max_body_size 2M; unless you need bigger uploads. This keeps maliciously large POSTs from exhausting memory.
Why each step matters: updating the package gives you patches, hiding version info removes an easy fingerprint, trimming modules shrinks the code that could be exploited, forcing modern TLS stops downgrade attacks, and capping body size prevents DoS via huge payloads.
Create a custom Fail2Ban filter for NGINX
- Make a new filter file – sudo nano /etc/fail2ban/filter.d/nginx-auth.conf.
- Paste the following pattern (adjust the regex if your logs differ):
[Definition]
failregex = ^<HOST> -.*"(GET|POST).*HTTP/.*" 403
^<HOST> -.*"(GET|POST).*wp-login\.php".* "401"
ignoreregex =
The first line catches any request that got a 403 – typical of bots probing forbidden paths. The second line looks for failed WordPress login attempts, which are the most common brute‑force vector on NGINX fronts.
- Save and exit – Ctrl+O, Enter, Ctrl+X.
I’ve seen this happen after a bad driver update: the server started spitting 403s to legitimate crawlers because a stray firewall rule blocked them. The filter above would have caught that spike early and let me investigate before users complained.
Enable the jail and test it
- Edit the jail config – sudo nano /etc/fail2ban/jail.local (or create one if it doesn’t exist). Add:
[nginx-auth]
enabled = true
port = http,https
filter = nginx-auth
logpath = /var/log/nginx/access.log
maxretry = 5
bantime = 3600
Setting maxretry to five means a single IP can only generate five of the defined bad events before it gets tossed for an hour. Adjust bantime if you need longer or shorter blocks.
Restart Fail2Ban – sudo systemctl restart fail2ban.
Verify the jail is active – run sudo fail2ban-client status nginx-auth. You should see “0 currently banned” and the path to your log file.
Force a test ban – from another terminal, try curl -I http://yourdomain/nonexistentpage repeatedly until you hit five 403s. Then check fail2ban-client status nginx-auth again; the offending IP should appear in the banned list.
Why testing matters: without it you could spend hours wondering why a rogue IP is still hitting your site, when in fact Fail2Ban never loaded the filter due to a syntax error. A quick manual trigger confirms everything is wired correctly.
Clean up and keep an eye on it
- Rotate logs – logrotate already handles NGINX logs, but make sure Fail2Ban’s own log (/var/log/fail2ban.log) isn’t growing unchecked.
- Review bans weekly – a mis‑tuned regex can accidentally block legitimate search engine bots. Run fail2ban-client unban <IP> if you spot a false positive.
That’s it. You now have a hardened NGINX serving only what you want and a Fail2Ban filter that actually knows when to step in.