Alternar tema

Guia completo de proxy reverso com Nginx: upstream, buffers e timeouts

Easton editorial illustration: one large reverse-proxy gate distributing traffic to three upstream servers

O celular começou a vibrar sem parar: era um alerta no ambiente de produção.

Ao abrir os logs, encontrei uma sequência de erros 502 Bad Gateway. O serviço de backend não tinha caído, mas o timeout na configuração do Nginx era curto demais. Assim que chegou o pico de tráfego, as requisições foram interrompidas à força antes de terminar. O culpado era aquele proxy_read_timeout 60s, cujo valor eu havia escolhido sem pensar muito.

Depois desse incidente, passei uma semana entendendo a fundo os três módulos centrais do proxy reverso do Nginx: balanceamento de carga com upstream, proxy buffers e configuração de timeouts. Quando os três estão bem configurados, o proxy reverso aguenta dez vezes mais tráfego. Quando estão errados, o resultado é tão ruim quanto o alerta daquela noite.

Este artigo reúne os problemas que enfrentei, o que aprendi durante o diagnóstico e os princípios que finalmente compreendi. Se você trabalha com backend ou operações, ou simplesmente quer entender a lógica por trás dos parâmetros do Nginx, este guia deve poupar bastante tempo.


Balanceamento de carga com upstream: não é apenas distribuir requisições

Primeiro, a sintaxe básica

O bloco de configuração upstream é o núcleo do balanceamento de carga do Nginx. Você provavelmente já viu a forma mais básica:

upstream backend {
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;
}

server {
    location / {
        proxy_pass http://backend;
    }
}

Parece simples: você define um grupo de servidores de backend e aponta proxy_pass para ele. Mas, sinceramente, saber apenas escrever isso não basta. Em produção, há muito mais a considerar: o que acontece se um servidor cair? Uma máquina mais potente pode receber mais tráfego? As conexões longas devem ser mantidas?

Quatro algoritmos de balanceamento, cada um para um cenário

Por padrão, o Nginx usa round-robin, distribuindo as requisições entre os servidores em sequência. É justo, mas não é inteligente.

Se o backend trabalha com conexões longas, como WebSocket ou pools de conexão com banco de dados, o round-robin pode fazer a quantidade de conexões de alguns servidores disparar. Nesse caso, o algoritmo de menor número de conexões, least_conn, é mais adequado:

upstream backend {
    least_conn;
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
}

Ele acompanha o número de conexões ativas de cada servidor e envia novas requisições para aquele que estiver mais livre. Em um projeto anterior, eu usava WebSocket para enviar mensagens em tempo real, e a memória de um dos servidores estourava com round-robin. Depois de mudar para least_conn, a carga ficou muito mais uniforme.

Há outro cenário que talvez você já tenha encontrado: depois que o usuário faz login, as requisições seguintes precisam chegar ao mesmo servidor, pois a sessão está armazenada localmente. O IP Hash serve para isso:

upstream backend {
    ip_hash;
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
}

As requisições do mesmo IP de cliente são direcionadas, por hash, a um backend fixo. Mas essa abordagem tem uma falha: se o servidor cair, a sessão será perdida. Uma solução realmente confiável é armazenar as sessões no Redis e tratar ip_hash apenas como medida temporária.

O quarto algoritmo é o hash consistente, muito usado em caches distribuídos:

upstream backend {
    hash $request_uri consistent;
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
}

O Nginx cria 160 nós virtuais por unidade de peso e direciona a requisição a um servidor específico com base no hash do URI. A vantagem é uma taxa maior de acerto do cache, pois o mesmo URI sempre chega à mesma máquina.

Pesos: o que fazer quando as máquinas têm capacidades diferentes

É bastante comum os servidores de backend terem configurações diferentes. Uma máquina pode ter 32 GB de memória e 8 núcleos de CPU, enquanto outra tem apenas 16 GB e 4 núcleos. Usar um round-robin perfeitamente uniforme desperdiçaria a capacidade da máquina mais potente.

upstream backend {
    server 192.168.1.10:8080 weight=3;
    server 192.168.1.11:8080 weight=2;
    server 192.168.1.12:8080 weight=1;
}

A máquina com weight=3 receberá três vezes mais requisições. As melhores máquinas trabalham mais e as menos potentes, menos. Essa distribuição faz mais sentido.

Há também o parâmetro backup, usado para um servidor reserva:

upstream backend {
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080 backup;
}

O servidor de backup normalmente não recebe carga. Ele só entra em operação quando os dois primeiros caem, como um reserva que vai a campo apenas quando os titulares saem.

Pool de conexões keepalive: o segredo para dobrar o desempenho

Muita gente ignora esta parte. Por padrão, o Nginx cria uma nova conexão TCP com o backend para cada requisição e a fecha assim que recebe a resposta. Parece inofensivo? Não é.

Estabelecer uma conexão TCP exige um handshake de três vias, e encerrá-la exige uma sequência de quatro etapas. Em cenários de alta concorrência, esse custo é enorme. Um pool keepalive permite reutilizar conexões e elimina grande parte dessa sobrecarga.

Exemplo de configuração:

upstream backend {
    server 192.168.1.10:8080;
    keepalive 32;  # Cada worker mantém 32 conexões ociosas
}

server {
    location / {
        proxy_pass http://backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }
}

Observe dois pontos:

  1. keepalive 32 define o número máximo de conexões ociosas mantidas por cada processo worker.
  2. É obrigatório configurar proxy_http_version 1.1 e Connection "", pois HTTP/1.0 não oferece suporte a conexões persistentes.

Em um teste anterior com um serviço de API, o QPS era de aproximadamente 2000 sem keepalive. Depois de habilitá-lo, passou diretamente de 4000. Dizer que dobrou não é exagero.

2 vezes
Aumento de QPS
Source: Dados de teste: após habilitar o pool de conexões keepalive

Mas é preciso tomar cuidado: não defina um valor alto demais para keepalive. Certa vez, configurei 100 em um ambiente de teste, mas o backend tinha apenas um contêiner ECS e acabou sobrecarregado pelo número de conexões. Uma fórmula aproximada para produção é:

keepalive ≈ QPS total ÷ tempo médio por requisição ÷ número de processos worker

Por exemplo, suponha um QPS estimado de 10000, tempo médio de resposta de 50 ms e 4 workers:

10000 × 0.05 ÷ 4 = 125

Nesse caso, definir keepalive em torno de 125 é razoável.

Verificação de integridade: remover automaticamente servidores indisponíveis

A versão de código aberto do Nginx oferece apenas verificação passiva de integridade: o servidor só é marcado como não saudável depois que uma requisição falha.

upstream backend {
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
}

server {
    location / {
        proxy_pass http://backend;
        proxy_next_upstream error timeout http_502 http_503 http_504;
        proxy_next_upstream_tries 3;
    }
}

proxy_next_upstream define em quais condições uma nova tentativa deve ser feita no servidor seguinte: erro de conexão, timeout ou resposta 502, 503 ou 504. proxy_next_upstream_tries 3 significa tentar no máximo três servidores.

Mas a verificação passiva tem atraso: é preciso esperar uma requisição falhar para descobrir que o servidor caiu. Se a sua aplicação exige alta disponibilidade, a verificação ativa do NGINX Plus é melhor:

upstream backend {
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
}

server {
    location / {
        proxy_pass http://backend;
        health_check interval=5s fails=3 passes=2;
    }
}

Uma requisição de verificação é enviada ativamente a cada 5 segundos. Três falhas consecutivas marcam o servidor como unhealthy, e ele só volta a ser considerado saudável depois de dois sucessos consecutivos.


Proxy buffers: o buffering ajuda ou atrapalha?

Para que serve o mecanismo de buffering

Primeiro, o conceito: depois que recebe uma resposta do backend, o Nginx não a envia diretamente ao cliente. Ele primeiro armazena os dados em buffers.

Por quê? Porque a velocidade da rede do cliente é imprevisível. O backend pode produzir os dados rapidamente, mas, se a conexão do cliente for lenta, o Nginx precisa esperar enquanto ele recebe tudo aos poucos. Com buffers, o Nginx armazena a resposta de uma vez e depois a envia gradualmente ao cliente. Assim, o backend não precisa esperar e pode começar a processar outra requisição mais cedo.

Mas o buffering também tem um custo: o consumo de memória. Com corpos de resposta grandes e alta concorrência, esse consumo pode ser significativo.

Três parâmetros centrais e a relação entre eles

proxy_buffer_size 4k;
proxy_buffers 8 32k;
proxy_busy_buffers_size 64k;

No início, esses três parâmetros me confundiam: os nomes são parecidos e as explicações parecem complicadas. Só entendi depois de desenhar um diagrama:

  • proxy_buffer_size: buffer que armazena o cabeçalho da resposta, um para cada requisição.
  • proxy_buffers: conjunto de buffers que armazena o corpo da resposta, no formato quantidade tamanho_de_cada_buffer.
  • proxy_busy_buffers_size: parte dos buffers que está sendo enviada ao cliente; não pode exceder metade do tamanho total de buffers.

Por exemplo, proxy_buffers 8 32k totaliza 8 × 32k = 256k. Com proxy_busy_buffers_size 64k, usamos exatamente um quarto desse total, dentro da regra.

Quando é necessário ajustar esses parâmetros?

Se o backend envia cabeçalhos de resposta muito grandes, por exemplo com muitos cookies, pode surgir o erro upstream sent too big header. A solução é aumentar proxy_buffer_size:

proxy_buffer_size 16k;

Se os corpos de resposta costumam ser grandes, como quando uma API retorna um JSON extenso, você pode aumentar os buffers:

proxy_buffers 16 64k;

Casos especiais: quando o buffering deve ser desativado

Em alguns cenários, o buffering atrapalha.

Server-Sent Events (SSE): o backend envia continuamente um fluxo de eventos. Se o Nginx retiver os dados no buffer, o cliente receberá as mensagens com atraso. É preciso desativar o buffering:

location /events {
    proxy_pass http://backend;
    proxy_buffering off;
    proxy_cache off;
    proxy_read_timeout 86400s;
}

proxy_read_timeout 86400s, ou um dia, é necessário porque SSE usa uma conexão longa, que não deve ser interrompida por timeout.

WebSocket: é semelhante, pois envolve comunicação bidirecional em tempo real:

location /ws {
    proxy_pass http://backend;
    proxy_buffering off;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_read_timeout 86400s;
}

Upload de arquivos grandes: se o cliente enviar um arquivo de 1 GB e o Nginx precisar recebê-lo por completo antes de encaminhá-lo ao backend, a memória pode se esgotar. Nesse caso, desative o buffering da requisição:

location /upload {
    proxy_pass http://backend;
    proxy_request_buffering off;
    client_max_body_size 1G;
}

proxy_request_buffering off faz o Nginx encaminhar os dados diretamente por streaming, recebendo e enviando ao mesmo tempo.


Timeouts: a lógica por trás da configuração

Três parâmetros de timeout, cada um com sua função

proxy_connect_timeout 10s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;

Os nomes parecem parecidos, mas as funções são bem definidas:

  • proxy_connect_timeout: tempo durante o qual o Nginx espera a conexão TCP ser estabelecida. Se o servidor de backend demorar a responder, por exemplo devido a congestionamento da rede ou bloqueio por firewall, a tentativa será abandonada quando esse prazo for excedido.
  • proxy_read_timeout: depois que a conexão é estabelecida, é o tempo que o Nginx aguarda dados do backend. Se o intervalo entre duas operações de leitura ultrapassar esse valor, ocorre timeout.
  • proxy_send_timeout: limite de tempo para o Nginx enviar o corpo da requisição ao backend.

Há um detalhe que costuma causar confusão: proxy_read_timeout não é o timeout total, mas o intervalo entre duas operações de leitura. Se o backend precisar de 5 minutos para processar uma requisição, mas continuar enviando algum dado durante o processamento, como pacotes de heartbeat, proxy_read_timeout 60s será suficiente. Se ele ficar completamente em silêncio por 5 minutos, será necessário usar proxy_read_timeout 300s.

A relação entre timeouts e erros 502/504

Com aquele alerta às três da manhã, aprendi uma lição importante:

  • 502 Bad Gateway: o Nginx não conseguiu sequer se conectar ao backend. O serviço pode ter caído, a porta pode estar inacessível ou um firewall pode ter bloqueado a conexão.
  • 504 Gateway Timeout: o Nginx se conectou, mas o backend demorou demais para devolver dados.

Por exemplo, com proxy_connect_timeout 10s, se o backend levar 15 segundos para aceitar a conexão, o Nginx retornará 502. Se a conexão for estabelecida rapidamente, mas o backend levar 2 minutos para começar a enviar dados e proxy_read_timeout estiver definido como 60s, a resposta será 504.

Estratégias de timeout para cenários diferentes

Serviços de API: normalmente, 30 a 60 segundos são suficientes. APIs devem responder rapidamente, e um timeout curto ajuda a detectar requisições lentas com antecedência:

proxy_connect_timeout 5s;
proxy_read_timeout 30s;
proxy_send_timeout 30s;

Processamento de arquivos: operações como exportar relatórios e gerar PDFs podem levar vários minutos. Os timeouts devem ser maiores:

proxy_connect_timeout 10s;
proxy_read_timeout 300s;
proxy_send_timeout 300s;

Serviços de streaming: transmissões de vídeo, WebSocket e SSE usam conexões longas; é normal configurar um dia inteiro:

proxy_read_timeout 86400s;

Diagnóstico prático de erros 502/504

Análise das causas

Estas são algumas situações que já encontrei:

  1. O serviço upstream realmente caiu: o processo falhou, a porta já estava ocupada ou a memória se esgotou.
  2. As conexões se esgotaram: o pool de conexões do servidor de backend ficou cheio e o Nginx não conseguiu se conectar.
  3. Os timeouts eram curtos demais: como no alerta daquela madrugada, proxy_read_timeout estava em 60s, mas o backend precisava de 2 minutos para processar a requisição.
  4. Problemas de firewall ou rede: as regras do security group não estavam configuradas ou o iptables bloqueou a requisição.

Como diagnosticar pelos logs

O primeiro passo é sempre verificar o error_log:

error_log /var/log/nginx/error.log warn;

Mensagens de erro comuns:

upstream timed out (110: Connection timed out) while reading response header from upstream

Esse é um erro 504 causado por timeout de leitura.

connect() failed (111: Connection refused) while connecting to upstream

Esse é um erro 502: a conexão foi recusada porque o backend não está escutando.

Uma abordagem mais avançada é personalizar o formato dos logs para registrar o estado do upstream:

log_format upstream_status '$status $upstream_status $upstream_response_time';

access_log /var/log/nginx/access.log upstream_status;

Você verá saídas como 200 200, 200, 502 0.5, 1.2, 3.0, que mostram claramente o código de status e o tempo de resposta de cada backend.

Soluções típicas

Cenário 1: o backend está lento e erros 504 são frequentes

Solução: aumente proxy_read_timeout e, ao mesmo tempo, confirme que o backend realmente consegue concluir o processamento. Não ajuste apenas o Nginx; os timeouts do backend também precisam ser atualizados.

Cenário 2: conexão recusada e erro 502

Solução: verifique se o processo do backend está em execução, se a porta está escutando e se as regras do firewall permitem a conexão.

netstat -tlnp | grep 8080
ps aux | grep your_app

Cenário 3: conexões esgotadas sob alta concorrência

Solução: aumente o limite do pool de conexões do backend ou habilite keepalive no upstream do Nginx para reduzir o custo de criação de conexões.


Práticas recomendadas de otimização de desempenho

Configuração dos workers

O Nginx usa um modelo multiprocesso. worker_processes define o número de processos, normalmente igual ao número de núcleos da CPU:

worker_processes auto;

O valor auto detecta automaticamente a quantidade de núcleos da CPU. Se a máquina tiver 8 núcleos, haverá 8 processos worker.

worker_connections é o número máximo de conexões que cada worker pode processar:

events {
    worker_connections 4096;
}

O número máximo teórico de conexões simultâneas é worker_processes × worker_connections. Com 8 núcleos × 4096, o resultado é 32768. Na prática, o valor também é limitado pelos descritores de arquivo do sistema.

Três recursos para otimizar o TCP

sendfile on;
tcp_nopush on;
tcp_nodelay on;

Usar esses três parâmetros em conjunto pode melhorar bastante o desempenho:

  • sendfile on: habilita a transferência de arquivos no nível do kernel, sem passar pelo buffer no espaço de usuário.
  • tcp_nopush on: em conjunto com sendfile, envia os pacotes em lotes em vez de um por vez.
  • tcp_nodelay on: envia pequenos pacotes imediatamente, sem esperar o buffer ficar cheio.

Em meus testes com um serviço de arquivos estáticos, a vazão aumentou mais de 30% depois de habilitar os três.

30%+
Aumento da vazão
Source: Dados de teste: após habilitar sendfile, tcp_nopush e tcp_nodelay

Outras otimizações

Compressão gzip: comprima respostas de texto antes da transmissão para economizar largura de banda:

gzip on;
gzip_types text/plain text/css application/json application/javascript;
gzip_min_length 1024;

Limite de descritores de arquivo: em alta concorrência, o limite pode ser insuficiente. Verifique o valor do sistema:

ulimit -n

Se ele for apenas 1024, será preciso aumentá-lo. Edite /etc/security/limits.conf:

* soft nofile 65535
* hard nofile 65535

Exemplo de configuração completa

Um modelo de configuração recomendado para produção:

# Configuração básica
worker_processes auto;

events {
    worker_connections 4096;
    multi_accept on;
}

http {
    # Otimização de TCP
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;

    # Keepalive
    keepalive_timeout 30;
    keepalive_requests 100;

    # Configuração dos buffers
    proxy_buffering on;
    proxy_buffer_size 4k;
    proxy_buffers 8 32k;
    proxy_busy_buffers_size 64k;

    # Configuração dos timeouts
    proxy_connect_timeout 10s;
    proxy_read_timeout 60s;
    proxy_send_timeout 60s;

    # gzip
    gzip on;
    gzip_types text/plain text/css application/json;

    upstream backend {
        least_conn;
        server 192.168.1.10:8080 weight=3;
        server 192.168.1.11:8080 weight=2;
        server 192.168.1.12:8080 backup;
        keepalive 32;
    }

    server {
        listen 80;

        location / {
            proxy_pass http://backend;
            proxy_http_version 1.1;
            proxy_set_header Connection "";
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;

            proxy_next_upstream error timeout http_502 http_503 http_504;
            proxy_next_upstream_tries 3;
        }

        # Configuração exclusiva para SSE
        location /events {
            proxy_pass http://backend;
            proxy_buffering off;
            proxy_read_timeout 86400s;
        }
    }
}

Conclusão

Depois de tudo isso, os pontos centrais se resumem a três:

  1. Configuração de upstream: escolha o algoritmo de balanceamento correto, habilite o pool de conexões keepalive e configure verificações de integridade.
  2. Configuração de buffers: entenda a relação entre os três parâmetros e desative o buffering nos casos especiais.
  3. Configuração de timeouts: entenda o papel de cada parâmetro e use estratégias diferentes conforme o cenário.

O incidente das três da manhã me ensinou uma coisa: configurar o Nginx não é apenas preencher parâmetros. Existe uma lógica de projeto por trás de cada um deles, e entender esses princípios é o que evita problemas.

Se você está começando a usar Nginx, recomendo partir da configuração padrão e fazer ajustes específicos conforme os problemas aparecerem. Não faça como eu, que coloquei proxy_read_timeout 60s em produção sem pensar muito. Se você já enfrentou problemas desse tipo, este artigo deve ajudar a transformar experiências dispersas em um entendimento mais estruturado.

Ao terminar este artigo, olhei a configuração do meu ambiente de produção atual: keepalive 32, proxy_read_timeout 120s e balanceamento com least_conn. O alerta das três da manhã nunca mais apareceu.


FAQ

proxy_read_timeout é um timeout total ou o intervalo entre duas leituras?
É o intervalo entre duas operações de leitura. Se o backend continuar enviando dados durante o processamento, como pacotes de heartbeat, proxy_read_timeout 60s será suficiente mesmo que o processamento total leve 5 minutos. Mas, se o backend ficar completamente em silêncio por 5 minutos, será preciso defini-lo como 300s.
Quando devo desativar proxy_buffering?
Ele deve ser desativado em três tipos de cenário:

• Server-Sent Events (SSE): o envio é em tempo real, e o buffering atrasa as mensagens
• WebSocket: a comunicação bidirecional em tempo real exige transmissão por streaming
• Upload de arquivos grandes: para evitar esgotar a memória, os dados devem ser recebidos e encaminhados ao mesmo tempo
Qual é um valor adequado para keepalive?
A fórmula é: keepalive ≈ QPS total × tempo médio por requisição ÷ número de processos worker. Por exemplo, com QPS de 10000, tempo de resposta de 50 ms e 4 workers, defina keepalive em torno de 125. Não use um valor alto demais — certa vez configurei 100 e sobrecarreguei o backend.
Qual é a diferença entre os erros 502 e 504?
502 Bad Gateway significa que o Nginx não conseguiu se conectar ao backend, porque o serviço caiu, a porta não está acessível ou um firewall bloqueou a conexão. 504 Gateway Timeout significa que a conexão foi estabelecida, mas a resposta excedeu o tempo limite, geralmente porque o backend está lento. Os diagnósticos são totalmente diferentes: para 502, verifique o processo e a porta; para 504, confira os timeouts e o tempo de processamento do backend.
Qual algoritmo de balanceamento de carga devo escolher?
Escolha conforme o cenário:

• Round-robin (padrão): serviços sem estado e distribuição uniforme
• least_conn: conexões longas, como WebSocket e pools de conexão com banco de dados
• ip_hash: quando é preciso manter a sessão, como solução temporária inferior ao uso do Redis
• hash: caches distribuídos, para aumentar a taxa de acerto
Como resolver o erro upstream sent too big header?
Aumente proxy_buffer_size. Cabeçalhos de resposta muito grandes no backend, por exemplo por conterem muitos cookies, podem exceder o buffer padrão de 4k. Alterar para proxy_buffer_size 16k costuma resolver.
Por que sendfile, tcp_nopush e tcp_nodelay melhoram o desempenho?
sendfile transfere os dados diretamente no kernel, sem passar pelo espaço de usuário; tcp_nopush envia pacotes em lotes e reduz sua quantidade; tcp_nodelay envia pequenos volumes imediatamente, sem esperar. Em conjunto, eles aumentaram em mais de 30% a vazão de arquivos estáticos nos testes.

14 min de leitura · Publicado em: 30 mar 2026 · Atualizado em: 4 set 2026

Comentários

Entre com GitHub para comentar

Easton BlogEaston Blog