KBeezie

There's no place like ::1

Menu
  • Home
  • Start Here
  • Security Series
  • About

Fail2ban with nftables and Crowd-Sourced Blacklists

2026/05/13 in Security

Step 9: Enable and test

# Start and enable fail2ban
sudo systemctl enable fail2ban
sudo systemctl start fail2ban

# Check jail status
sudo fail2ban-client status
sudo fail2ban-client status sshd
sudo fail2ban-client status nginx-forbidden
sudo fail2ban-client status nginx-ratelimit
sudo fail2ban-client status nginx-404

To test the nginx-forbidden jail, trigger a 403 intentionally — request a blocked path from an external IP:

curl -I https://yoursite.com/wp-config.php

Repeat three times within 2 hours. Check that the IP appears in the banned list:

sudo fail2ban-client status nginx-forbidden
sudo nft list set inet f2b-table abuse-blacklist | head -20

Test the nginx-ratelimit jail by hitting a PHP endpoint rapidly:

for i in $(seq 1 20); do curl -s -o /dev/null -w "%{http_code}\n" https://yoursite.com/; done

After 15+ rapid requests you should see 429 responses. Check the jail:

sudo fail2ban-client status nginx-ratelimit

Verify the blacklist script works by running it manually once:

sudo /usr/local/bin/update-abuse-blacklist.sh

Step 10: Production hardening

Cloudflare and reverse proxies: If you use Cloudflare or any reverse proxy with ngx_http_realip_module, nginx gives you two client-address variables:

VariableContains
$remote_addrThe real visitor IP (overwritten by realip from CF-Connecting-IP or X-Forwarded-For)
$realip_remote_addrThe proxy IP — preserved original (Cloudflare's edge, your load balancer)

$binary_remote_addr is the compact binary form of $remote_addr — the visitor IP after realip processing. This is the variable you want for per-visitor rate and connection limiting.

If you accidentally key limit_req_zone on $realip_remote_addr, all visitors behind the same Cloudflare edge share one bucket. One bot burns the rate limit; legitimate visitors get throttled or 429'd. On a small site with only a few Cloudflare edge IPs, this silently collapses your rate limiting to a handful of shared buckets.

To verify which values your variables hold, add a temporary debug location and hit it through your proxy:

location /debug-realip {
    allow YOUR_HOME_IP;
    deny all;
    default_type application/json;
    add_header X-Remote-Addr "$remote_addr" always;
    add_header X-RealIP-Remote-Addr "$realip_remote_addr" always;
    return 200 '{"remote_addr":"$remote_addr","realip_remote_addr":"$realip_remote_addr"}';
}

Hit it through Cloudflare and inspect the response headers. X-Remote-Addr should be your real IP; X-RealIP-Remote-Addr should be a Cloudflare IP. Remove the location after testing.

SSH on systemd systems: Modern OpenSSH logs authentication failures from sshd-session, not sshd. If your server uses the systemd backend (the default on Debian/Ubuntu), add this to /etc/fail2ban/jail.d/defaults-debian.conf:

[sshd]
backend = systemd
journalmatch = _SYSTEMD_UNIT=ssh.service + _COMM=sshd + _COMM=sshd-session
enabled = true

Without sshd-session in the match, fail2ban sees almost no authentication failures. This requires a full restart — sudo systemctl restart fail2ban — not a reload. A reload preserves the journal subscription; only a restart tears it down and re-establishes with the new match string.


What this setup protects against

Daily blacklist (AbuseIPDB + Bitwire + Spamhaus) — Known bad actors before they ever send a packet. ISP netblocks run by criminals, IPs with thousands of abuse reports, and addresses flagged across the community are dropped at the firewall before they reach Nginx.

fail2ban (nginx-forbidden) — Bots probing your specific site right now. Hitting wp-config.php, xmlrpc.php, .env files, and other protected paths triggers a 403, which fail2ban detects and bans on repeat — with each offense reported to AbuseIPDB.

fail2ban (nginx-ratelimit) — Bots that bypass forbidden rules by probing random framework paths that return 404 through the PHP stack. Rate limiting the PHP handler caps their throughput, and repeated 429s trigger a ban.

fail2ban (nginx-404) — Scanners that enumerate non-existent paths without hitting PHP or forbidden rules. High-speed 404 storms trigger a short ban, breaking the scanner's momentum without risking false positives for normal users.

fail2ban (sshd) — SSH brute-force attempts caught early, banned for escalating durations with the incremental formula, and reported to AbuseIPDB so other servers know about the attacker too.

The blacklist catches the drive-by scanner before it even reaches Nginx. fail2ban catches the targeted bot that's specifically probing your site. Together they cover both the generic noise and the directed attacks.

Technical Audit Summary

This guide is maintained as part of a modular, SSL-first framework. Each configuration is audited for production stability and modern security standards.

Last Audit: May 2026
Environment: Debian Trixie (13)
Nginx: 1.30.1
PHP-FPM: 8.5.6

Compatibility: Tested against current stable releases. While optimized for the stack above, core logic remains relevant for Nginx 1.26+ and PHP 8.2+ environments.

2026-05-12: Updated Step 5a failregex after a real-world URL‑encoded .env scanner bypassed the default __prefix_line filter. Replaced with explicit timestamp‑aware patterns; documented why the shipped default fails on most Nginx error‑log formats.

2026-05-13: Added Step 5b and nginx-ratelimit jail — rate‑limiting the PHP handler with limit_req and a nginx-ratelimit fail2ban jail to catch 404 crawl bots that bypass the forbidden filter. Triggered by a real curl/8.7.1 scanner probing 40+ encoded framework paths through WordPress.

2026-05-15: Full-stack production audit — fixed $realip_remote_addr vs $binary_remote_addr rate-limit keying, corrected SSH journalmatch to include sshd-session, added bantime.maxtime caps, added nginx-404 jail for 404-storm scanners, added .well-known exceptions for traffic-advice/tdmrep.json, centralized ignoreip in [DEFAULT]. MariaDB buffer pool halved, PHP-FPM memory limit and child count tuned for 2 GB VPS.

  • ← Previous
  • 1
  • 2
  • 3
  • 4
  • Next →
Tags: fail2ban, nftables, abuseipdb, spamhaus, bitwire, blacklist, hardening
©2026 KBeezie | Disclaimer | Privacy Notice