Rate limiting avançado com Nginx

Rate limiting no Nginx protege a API contra abuso, brute force e DDoS de camada 7 sem depender de software externo. Com limit_req_zone e limit_conn_zone, você controla quantas requisições por segundo cada IP pode fazer, granularmente por rota, método HTTP ou até por usuário autenticado.

Configurar zonas de rate limiting

Defina zonas de memória para rastrear contadores por IP:

nginx
# /etc/nginx/nginx.conf — dentro do bloco http {}

# Zona de rate limit por IP (10m de memória = ~160.000 IPs)
limit_req_zone $binary_remote_addr zone=api_rate:10m rate=10r/s;

# Zona para rotas sensíveis (login, cadastro)
limit_req_zone $binary_remote_addr zone=auth_rate:10m rate=3r/m;

# Zona para uploads (1 por segundo por IP)
limit_req_zone $binary_remote_addr zone=upload_rate:5m rate=1r/s;

# Zona de conexões simultâneas por IP
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;

# Logging de requisições limitadas (level warning)
limit_req_log_level warn;
limit_req_status 429;          # retornar HTTP 429 em vez de 503

# Cabeçalhos para informar o cliente sobre o limite:
# (disponível no Nginx 1.23+)
# limit_req_dry_run on;        # testar sem bloquear (apenas logar)

Aplicar rate limiting por rota

Aplicar limites diferentes para cada endpoint da API:

nginx
server {
    listen 443 ssl http2;
    server_name api.seudominio.com.br;

    # Rate limit padrão para toda a API: 10 req/s com burst de 20
    location /api/ {
        limit_req zone=api_rate burst=20 nodelay;
        limit_conn conn_limit 20;
        proxy_pass http://127.0.0.1:3000;
    }

    # Login: máx 3 tentativas por minuto por IP
    location /api/auth/login {
        limit_req zone=auth_rate burst=5 nodelay;
        proxy_pass http://127.0.0.1:3000;
    }

    # Cadastro: máx 3 por minuto
    location /api/auth/register {
        limit_req zone=auth_rate burst=3 nodelay;
        proxy_pass http://127.0.0.1:3000;
    }

    # Upload de arquivos: 1 por segundo, máx 3 simultâneos
    location /api/upload {
        limit_req zone=upload_rate burst=3;
        limit_conn conn_limit 3;
        client_max_body_size 100M;
        proxy_pass http://127.0.0.1:3000;
    }

    # Healthcheck: sem rate limit
    location /health {
        proxy_pass http://127.0.0.1:3000;
    }
}

Whitelist e limites diferenciados por IP

Isentar IPs internos e aplicar limites diferentes por origem:

nginx
# /etc/nginx/conf.d/rate-limit-map.conf
# Mapa para definir limite por IP de origem
geo $limit_key {
    default         $binary_remote_addr;  # IPs não listados: limite por IP
    10.0.0.0/8      "";                   # rede interna: sem limite (key vazia)
    177.10.0.1/32   "";                   # IP do escritório: sem limite
    192.168.0.0/16  "";                   # rede local: sem limite
}

# Zona usando a key condicional
limit_req_zone $limit_key zone=api_conditional:10m rate=10r/s;

# No server block:
location /api/ {
    # IPs internos ($limit_key = "") não são limitados
    # IPs externos são limitados por $binary_remote_addr
    limit_req zone=api_conditional burst=20 nodelay;
    proxy_pass http://127.0.0.1:3000;
}

# Alternativa: mapa de limites por tier
map $http_x_api_key $rate_limit_zone {
    default             "default_zone";
    "chave_premium_1"   "";             # sem limite para clientes premium
    "chave_premium_2"   "";
}

Resposta customizada para requisições limitadas

Retornar JSON adequado quando o rate limit é atingido:

nginx
# /etc/nginx/conf.d/errors.conf

# Página de erro customizada para 429
error_page 429 /rate-limit.json;

location = /rate-limit.json {
    internal;
    default_type application/json;
    return 429 '{"error":"rate_limit_exceeded","message":"Muitas requisições. Tente novamente em alguns segundos.","status":429}';
}

# No bloco server — adicionar headers Retry-After:
location /api/ {
    limit_req zone=api_rate burst=20 nodelay;

    # Header informando quanto tempo aguardar:
    add_header Retry-After 1 always;
    add_header X-RateLimit-Limit 10 always;

    proxy_pass http://127.0.0.1:3000;
}

Monitorar rate limiting nos logs

Identificar IPs que atingem o rate limit frequentemente:

bash
# Contar IPs que receberam 429 nas últimas horas:
sudo grep "limiting requests" /var/log/nginx/error.log \
  | awk '{print $NF}' \
  | sort | uniq -c | sort -rn | head -20

# Identificar endpoints mais limitados:
sudo awk '$9 == 429 {print $7}' /var/log/nginx/access.log \
  | sort | uniq -c | sort -rn | head -10

# Script para bloquear IPs abusivos com fail2ban:
# /etc/fail2ban/filter.d/nginx-ratelimit.conf
[Definition]
failregex = limiting requests, excess:.* by zone .*, client: <HOST>
ignoreregex =

# /etc/fail2ban/jail.d/nginx-ratelimit.conf
[nginx-ratelimit]
enabled = true
filter = nginx-ratelimit
logpath = /var/log/nginx/error.log
maxretry = 10
findtime = 60
bantime = 3600

Não quer configurar manualmente?

Implante o VPS em menos de 5 minutos com a Runstack. Infraestrutura da OPEN Datacenter, com servidores em São Paulo.

Perguntas frequentes

Exemplo: rate=10r/s burst=20. Sem nodelay: se chegarem 30 requisições de uma vez, 10 são processadas imediatamente, 20 ficam na fila (atrasadas em 100ms, 200ms... até 2s), e as demais retornam 429. Com nodelay: as 30 são processadas imediatamente, mas o contador registra 30 tokens usados, as próximas 30 precisam esperar ~2 segundos. nodelay é melhor para APIs pois não adiciona latência artificial.

1 MB armazena ~16.000 entradas de estado (binary_remote_addr = 4 bytes para IPv4 + overhead). 10 MB ≈ 160.000 IPs simultâneos. Para a maioria das VPS: 10m é mais que suficiente. Se você tem tráfego de centenas de milhares de IPs únicos por hora: considere 50m-100m.

O Nginx não decodifica JWT nativamente, mas você pode usar o user ID extraído pelo backend como chave. Opção 1: o backend adiciona um header X-User-ID ao retornar para o Nginx (em proxy reverso reverso). Opção 2: use o módulo auth_request para validar o JWT e definir variáveis. Opção 3: rate limit por IP no Nginx + rate limit por usuário na própria aplicação.

Nginx: mais eficiente (não chega na aplicação), protege contra DDoS e brute force, sem código extra. Aplicação: mais granular (por usuário, por endpoint, por tenant), pode retornar informações de quota restante nos headers. Recomendação: Nginx para proteção básica por IP + lógica de rate limit de negócio na aplicação.

Use Apache Bench: ab -n 100 -c 50 https://api.seudominio.com.br/api/test, observe quantas respostas retornam 429. Ou com curl em loop: for i in $(seq 1 30); do curl -s -o /dev/null -w "%{http_code}\n" https://api.seudominio.com.br/api/test; done. Espere ver 200 por alguns requests e depois 429 quando o burst for esgotado.