Rate limit no wp-login.php com nginx: barrar força bruta antes do PHP

O servidor sobe de CPU do nada e o access.log está entupido de POST /wp-login.php. Plugin de limite de tentativas não resolve esse caso: ele só age depois que o PHP montou o WordPress inteiro, e é justamente esse carregamento que está comendo o processador. O corte tem que ser no nginx.

A zona e o location exato

A zona vive no bloco http e é compartilhada por todos os sites. Dez megabytes seguram algo perto de 160 mil endereços:

# em /etc/nginx/nginx.conf, dentro de http { }
limit_req_zone $binary_remote_addr zone=login:10m rate=2r/m;

# no server { } do site
location = /wp-login.php {
    limit_req zone=login burst=3 nodelay;
    limit_req_status 429;

    include snippets/fastcgi-php.conf;
    fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}

O rate=2r/m libera uma requisição a cada 30 segundos por IP. O burst=3 guarda três excedentes numa fila e o nodelay manda essas três na hora, em vez de segurar. Quem passar disso leva 429 e nunca encosta no FPM.

Conferindo o efeito

A requisição barrada aparece sim no access.log, com status 429 (campo 9 no formato combined). O motivo, com IP e nome da zona, o nginx joga no log de erro, porque limit_req_log_level vem em error por padrão:

sudo nginx -t && sudo systemctl reload nginx
sudo awk '$9 == 429' /var/log/nginx/access.log | tail -20
sudo grep "limiting requests" /var/log/nginx/error.log | tail -20

Duas pegadinhas. A primeira: location = /wp-login.php é match exato e vence o location ~ .php$, então se você esquecer o fastcgi_pass ali dentro o nginx passa a oferecer o arquivo como download e a tela de login some. A segunda: atrás de Cloudflare ou de um balanceador, $binary_remote_addr é o IP do proxy, e sem o módulo real_ip configurado você limita todo mundo de uma vez.

Deixe rodando um dia e releia o error.log. Se aparecer IP de gente de verdade sendo cortada, afrouxe pra 5r/m.

Dúvidas? Faça um comentário logo abaixo ou envie uma mensagem clicando aqui.

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *