fail2ban needs a jail per log, not a jail per server

Installing fail2ban is treated as a step that protects the server. What it actually gives you is one jail — [ssh], watching auth.log — while the same brute force is running against wp-login.php in the nginx access log and SMTP AUTH in the mail log, neither of which anything is reading.

# /etc/fail2ban/jail.local — never jail.conf, which is replaced on upgrade
[ssh]
enabled  = true
port     = ssh
filter   = sshd
logpath  = /var/log/auth.log
maxretry = 4

[wp-login]
enabled  = true
port     = http,https
filter   = wp-login
logpath  = /var/log/nginx/access.log
maxretry = 5
findtime = 300
bantime  = 3600

# /etc/fail2ban/filter.d/wp-login.conf
[Definition]
failregex = ^<HOST> .* "POST /wp-login.php
ignoreregex =

A jail is a filter, a log path and an action bound together, so it watches exactly one file — which means the unit of configuration is the log, not the machine. The setting that quietly wastes the effort is port: the ban is written into iptables for the ports the jail names, so an HTTP attacker banned by a jail that says port = ssh carries on hitting the login form unimpeded. Test a new filter with fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/wp-login.conf before enabling it, because a regex that matches nothing fails silently and the jail simply never bans anyone. And behind a proxy or a CDN the address in the log is the proxy’s, so the jail will eventually ban that instead.