A login page with no limit can be tried as fast as the network allows. A limit does not make a weak password strong; it makes guessing take longer than anyone will wait, which in practice is the same thing.
At the web server
limit_req_zone zone=login:10m rate=5r/m;
location = /wp-login.php {
limit_req zone=login burst=3 nodelay;
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
Five a minute with a burst of three: nobody types faster, and a machine is stopped dead. Do this at the server, not in the application - a request refused here never reaches PHP at all.
Watch it before you enforce it
grep -c "limiting requests" /var/log/nginx/error.log
A limit set too low locks out real people, and they do not report it - they leave. Watch the log for a week at a generous rate before tightening.
Ban repeat offenders
# /etc/fail2ban/jail.local
[nginx-limit-req]
enabled = true
filter = nginx-limit-req
logpath = /var/log/nginx/error.log
maxretry = 10
bantime = 3600
What a limit does not do
- It does not stop a distributed attempt from a thousand addresses, each trying twice.
- It does not help if the password already leaked - the attacker needs one attempt.
Two-factor makes rate limiting almost unnecessary and rate limiting does not make two-factor unnecessary. If you only do one, do two-factor.