Como configurar rate limiting no Nginx

1. Introdução ao Rate Limiting no Nginx

Rate limiting é uma técnica essencial para proteger servidores web e APIs contra abusos, ataques de negação de serviço (DoS) e uso excessivo de recursos. No Nginx, o módulo ngx_http_limit_req_module oferece uma implementação robusta e flexível para controlar a taxa de requisições.

A importância do rate limiting se manifesta em diversos cenários:
- Proteção de APIs: Evita que um único cliente consuma todos os recursos
- Mitigação de ataques: Reduz o impacto de ataques de força bruta e DDoS
- Garantia de qualidade: Mantém a experiência do usuário durante picos de tráfego
- Controle de custos: Limita o uso de recursos em ambientes com cobrança por requisição

O Nginx gerencia o rate limiting através de duas diretivas principais: limit_req_zone, que define as zonas de memória compartilhada para rastreamento, e limit_req, que aplica os limites às requisições.

O rate limiting pode ser baseado em diferentes critérios:
- Por IP: Utilizando $binary_remote_addr para identificar cada cliente
- Por chave personalizada: Combinando variáveis como $http_x_api_key ou $uri
- Por geolocalização: Usando módulos GeoIP para limitar regiões específicas

2. Configuração Básica com limit_req_zone

A diretiva limit_req_zone define uma zona de memória compartilhada onde o Nginx armazena o estado de cada chave de limitação. A sintaxe básica é:

limit_req_zone $binary_remote_addr zone=minhazona:10m rate=10r/s;

Parâmetros essenciais:
- $binary_remote_addr: Chave baseada no endereço IP do cliente (formato binário para economia de memória)
- zone=minhazona:10m: Nome da zona e tamanho da memória compartilhada (10 MB)
- rate=10r/s: Taxa máxima de 10 requisições por segundo

Exemplo prático completo:

http {
    # Define a zona de rate limiting
    limit_req_zone $binary_remote_addr zone=limite_ip:10m rate=10r/s;

    server {
        listen 80;
        server_name exemplo.com;

        location /api/ {
            # Aplica o limite definido na zona
            limit_req zone=limite_ip;
            proxy_pass http://backend_api;
        }
    }
}

3. Aplicação de Limites com limit_req

A diretiva limit_req aplica os limites definidos nas zonas. Ela pode ser usada nos blocos http, server ou location.

Controle de burst (rajadas):

location /api/ {
    limit_req zone=limite_ip burst=20;
    proxy_pass http://backend_api;
}

O parâmetro burst permite que requisições excedentes sejam enfileiradas temporariamente, em vez de serem rejeitadas imediatamente.

Gerenciamento com nodelay:

location /api/ {
    limit_req zone=limite_ip burst=20 nodelay;
    proxy_pass http://backend_api;
}
  • Sem nodelay: Requisições em burst são atrasadas para respeitar a taxa
  • Com nodelay: Requisições em burst são processadas imediatamente, mas o burst é consumido mais rapidamente

4. Personalização de Respostas e Erros

Quando o limite é excedido, o Nginx retorna o código de status 503 por padrão. Podemos personalizar essa resposta:

Alterando o código de status:

location /api/ {
    limit_req zone=limite_ip burst=20;
    limit_req_status 429;
    proxy_pass http://backend_api;
}

Customizando a mensagem de erro:

location /api/ {
    limit_req zone=limite_ip burst=20;
    limit_req_status 429;

    error_page 429 /custom_429.html;

    location = /custom_429.html {
        internal;
        root /usr/share/nginx/html;
    }
}

Adicionando cabeçalho Retry-After:

location /api/ {
    limit_req zone=limite_ip burst=20;
    limit_req_status 429;

    # Adiciona cabeçalho Retry-After
    add_header Retry-After 60 always;
    proxy_pass http://backend_api;
}

5. Estratégias Avançadas de Rate Limiting

Limitação por múltiplas zonas:

http {
    # Limite por IP
    limit_req_zone $binary_remote_addr zone=por_ip:10m rate=10r/s;

    # Limite por chave de API
    limit_req_zone $http_x_api_key zone=por_chave:10m rate=100r/s;

    server {
        location /api/ {
            # Aplica ambos os limites
            limit_req zone=por_ip burst=20;
            limit_req zone=por_chave burst=200;
            proxy_pass http://backend_api;
        }
    }
}

Uso de variáveis personalizadas:

http {
    # Combina IP com caminho da requisição
    limit_req_zone $binary_remote_addr$uri zone=por_ip_uri:10m rate=5r/s;

    # Limita por método HTTP
    map $request_method $limit_key {
        default "";
        POST $binary_remote_addr;
    }
    limit_req_zone $limit_key zone=post_limit:10m rate=2r/s;
}

Combinação com limit_conn para controle de conexões:

http {
    limit_req_zone $binary_remote_addr zone=req_limit:10m rate=10r/s;
    limit_conn_zone $binary_remote_addr zone=conn_limit:10m;

    server {
        location /api/ {
            limit_req zone=req_limit burst=20;
            limit_conn conn_limit 10;
            proxy_pass http://backend_api;
        }
    }
}

6. Monitoramento e Logs de Rate Limiting

Configuração de logs detalhados:

http {
    log_format rate_limit '$remote_addr - $remote_user [$time_local] '
                          '"$request" $status $body_bytes_sent '
                          '"$http_referer" "$http_user_agent" '
                          'limit=$limit_req_status';

    server {
        access_log /var/log/nginx/access.log rate_limit;

        location /api/ {
            limit_req zone=limite_ip burst=20;
            proxy_pass http://backend_api;
        }
    }
}

Identificação de requisições limitadas: Os logs conterão registros com código 503 ou 429, indicando requisições que excederam os limites.

Análise com stub_status:

server {
    location /nginx_status {
        stub_status on;
        allow 127.0.0.1;
        deny all;
    }
}

7. Testes e Validação da Configuração

Teste básico com curl:

# Teste de requisição normal
curl -I http://localhost/api/teste

# Teste de rate limiting (enviar 15 requisições rapidamente)
for i in {1..15}; do curl -s -o /dev/null -w "%{http_code}\n" http://localhost/api/teste; done

Simulação de carga com ab (Apache Benchmark):

# 100 requisições, 10 concorrentes
ab -n 100 -c 10 http://localhost/api/teste

Teste com wrk para cenários mais realistas:

# 10 conexões por 30 segundos
wrk -t2 -c10 -d30s http://localhost/api/teste

Validação de burst e nodelay:

# Envia 30 requisições rapidamente (burst de 20 + taxa normal)
for i in {1..30}; do curl -s -o /dev/null -w "%{http_code} " http://localhost/api/teste; done
echo ""

8. Boas Práticas e Considerações Finais

Escolha adequada de taxa e burst:
- Analise o tráfego normal para definir limites realistas
- Comece com valores conservadores e ajuste gradualmente
- Considere o comportamento de bots e crawlers legítimos

Impacto em cache e balanceamento de carga:
- Rate limiting pode afetar a eficiência do cache
- Em ambientes com múltiplos servidores, use sticky sessions ou armazenamento compartilhado
- Considere o uso de limit_req_zone com variáveis que incluam o servidor de destino

Documentação e versionamento:
- Mantenha as configurações em arquivos versionados (Git)
- Documente cada zona de rate limiting com seu propósito
- Estabeleça procedimentos para revisão periódica dos limites

Exemplo de configuração final completa:

http {
    # Zonas de rate limiting
    limit_req_zone $binary_remote_addr zone=limite_geral:10m rate=10r/s;
    limit_req_zone $http_x_api_key zone=limite_api:10m rate=100r/s;

    # Log personalizado
    log_format rate_limit '$remote_addr - $remote_user [$time_local] '
                          '"$request" $status $body_bytes_sent '
                          'limit_status=$limit_req_status';

    server {
        listen 80;
        server_name api.exemplo.com;

        access_log /var/log/nginx/api_access.log rate_limit;

        location /api/v1/ {
            limit_req zone=limite_geral burst=20 nodelay;
            limit_req zone=limite_api burst=200 nodelay;
            limit_req_status 429;

            add_header Retry-After 60 always;
            error_page 429 /429.html;

            proxy_pass http://backend_api;
        }

        location = /429.html {
            internal;
            root /usr/share/nginx/html;
        }
    }
}

Rate limiting é uma ferramenta poderosa quando configurada corretamente. A chave está em encontrar o equilíbrio entre proteção e usabilidade, ajustando os parâmetros com base em dados reais de tráfego e feedback dos usuários.

Referências