Rate limit atrás de proxy: por que bloqueou todo mundo de uma vez?

Sem trust proxy, o Express vê o IP do proxy em toda requisição e o rate limit vira um teto único. Como declarar o número de saltos e por que não usar true.

Pedro Henrique Quadroatualizado em 4 min de leitura

Porque, sem avisar o Express que existe um proxy na frente, ele enxerga o endereço do proxy como se fosse o do cliente. Todo mundo passa a ter o mesmo IP, e o rate limit (o limite de tentativas por pessoa) vira um teto único da aplicação inteira. Num login limitado a 30 tentativas, 30 tentativas somadas de todos os usuários travam o login de todos. A correção é declarar quantos proxies existem na frente com app.set('trust proxy', N). Conferi as fontes em 10 de outubro de 2026.

O que a documentação diz

Proxy reverso é o servidor que fica na frente da aplicação e repassa as requisições, como o nginx, uma CDN ou um balanceador. Quando ele repassa, o endereço real do visitante vai num cabeçalho chamado X-Forwarded-For.

A documentação do Express explica que, por padrão (trust proxy desligado), o app se considera diretamente exposto ao cliente e deriva o IP de req.socket.remoteAddress. Atrás de um proxy, isso devolve o endereço interno do proxy. A documentação cita esse como o problema mais comum.

Com a configuração ligada, req.ip e req.ips passam a ser calculados a partir do endereço do socket e do X-Forwarded-For. Se o valor for um número, ele indica quantos saltos confiar: o primeiro salto é o socket, e os demais são procurados no cabeçalho, da direita para a esquerda. O valor 0 significa que não há proxy.

A documentação do express-rate-limit descreve os dois sintomas desse erro. Um aviso (ERR_ERL_UNEXPECTED_X_FORWARDED_FOR) aparece quando o cabeçalho chega mas trust proxy está desligado, e o texto diz que é uma configuração que faz o limite valer de forma global, e não por usuário. O outro (ERR_ERL_PERMISSIVE_TRUST_PROXY) aparece quando trust proxy é true, porque aí o IP é a entrada mais à esquerda do cabeçalho, que o cliente ou o proxy podem definir.

O que vi em produção

Encontrei isso em 29 de julho de 2026, ainda ativo em produção, numa API Express atrás de nginx. Em desenvolvimento nada aparecia, porque lá não há proxy.

Mandei cinco requisições com X-Forwarded-For diferentes para uma rota de login:

  • sem a declaração: respostas 409, 409, 429, 429, 429, e req.ip sempre 127.0.0.1 (o 409 era a resposta normal da rota para o pedido de teste; o 429 é "muitas requisições", o bloqueio do limitador);
  • com trust proxy igual a 1: 409 nas cinco.

O 429 é "muitas tentativas". Ou seja, sem a declaração, tentativas de endereços diferentes consumiam o mesmo balde, e com ela nenhuma foi barrada.

É uma falha de disponibilidade, não de segurança: 30 requisições baratas viravam negação de serviço do login.

Por que o true é pior

Parece tentador resolver com app.set('trust proxy', true). A documentação do Express avisa que, com true, o último proxy confiável precisa remover ou sobrescrever X-Forwarded-For, X-Forwarded-Host e X-Forwarded-Proto, senão o cliente pode informar o valor que quiser. Para o rate limit, isso significa que basta mudar o cabeçalho a cada requisição para ter tentativas infinitas. É pior que não ter limite, porque parece protegido.

Pelo mesmo motivo, eu não resolvo lendo o cabeçalho na mão num gerador de chave: o primeiro item do X-Forwarded-For é forjável pelo cliente. Declarando o número de saltos, o próprio framework faz a leitura correta.

Efeito colateral

Os pontos do código que gravam req.ip (IP do aceite de termos, logs, registro de cobrança) estavam guardando o endereço do proxy, que não identifica ninguém. Depois da correção passam a guardar o real. Antes de temer essa mudança, confira se algum trecho já lia o cabeçalho na mão para contornar. No meu caso, o aceite de contrato já fazia isso, então nada mudou ali.

O que fazer

  1. Conte os saltos entre o cliente e a aplicação: 1 para cliente, nginx, app; 2 se houver uma CDN na frente. Declare esse número, nunca true.
  2. Confira o resultado mandando requisições com X-Forwarded-For diferentes e olhando req.ip e as respostas.
  3. Escreva um teste que afirme a configuração contra os dois erros opostos, zero saltos e true, e um caso provando que quem forja o cabeçalho continua preso ao endereço real.
  4. Não ignore o aviso do express-rate-limit: ele existe para pegar exatamente isto.
// cliente -> nginx -> app: um salto
app.set('trust proxy', 1);

Se você quer um sistema web com autenticação e limites bem configurados desde o começo, veja sistemas e SaaS.

Fontes

  1. Express: atrás de proxies, trust proxy, req.ip e número de saltos
  2. express-rate-limit: códigos de erro ERR_ERL_UNEXPECTED_X_FORWARDED_FOR e ERR_ERL_PERMISSIVE_TRUST_PROXY
Pedro Henrique QuadroEngenheiro de software e IA. Constrói aplicativos, sistemas, painéis e agentes de IA em produção, e tem código aceito no Supabase, no Kestra e no QuestDB.

Continue lendo