Timeouts e erros de load balancer: 502, 503 e 504 explicados

Um 504 significa que o load balancer desistiu de esperar o seu app. Um 502 significa que ele recebeu uma resposta inválida ou uma conexão encerrada. Compare o timeout de cada etapa para achar o culpado.

Atualizado em 8 min de leitura

Objetivos de aprendizado

Depois de ler este artigo, você vai conseguir:

  • Diferenciar 502, 503 e 504 e saber qual etapa os gerou
  • Descobrir qual timeout (conexão, leitura, ociosidade, keep-alive) a requisição atingiu
  • Corrigir a causa: handlers lentos, keep-alive desalinhado, backends fora do ar
Nesta página

Você abre o seu site e vê uma página crua: 504 Gateway Timeout. Ou 502 Bad Gateway. Ou funciona nove vezes em dez e falha na décima. Os logs do app não mostram nada, porque muitas vezes a requisição nem chegou ao seu código, ou chegou tarde demais.

Esses erros são gerados pelo que está na frente do app: um load balancer, um proxy reverso ou uma CDN. Cada um tem o próprio relógio, e o primeiro que estoura decide o erro. O código de status e o tempo até a falha dizem qual relógio foi.

text
504 Gateway Timeout
502 Bad Gateway
503 Service Unavailable
tempo de esperaNavegadorCDN524Load balancer504Proxy reverso504estoura primeiroApphandler lentoSem timeout: 502 resposta inválida, 503 sem backend
Cada etapa tem o próprio relógio; o primeiro que estoura decide o erro que chega ao navegador.

O que significam 502, 503 e 504?#

Os três são erros do lado do servidor definidos na RFC 9110, e quando um proxy os devolve, em geral querem dizer "o proxy está bem, o que está atrás dele não".

Código Significado Causa típica
502 Bad Gateway O proxy recebeu uma resposta inválida do upstream App caiu no meio da resposta, conexão resetada, protocolo ou porta errados
503 Service Unavailable O servidor não consegue atender agora (sobrecarga ou manutenção) Nenhum backend saudável, app sobrecarregado, modo de manutenção proposital
504 Gateway Timeout O proxy não recebeu resposta do upstream a tempo Handler lento, event loop travado, consulta lenta no banco

Um jeito curto de lembrar: 502 é "recebi lixo ou nada", 503 é "ninguém consegue atender", 504 é "esperei e você não respondeu".

Algumas CDNs usam códigos próprios. A Cloudflare devolve 524 quando conectou na sua origem, mas não recebeu resposta HTTP a tempo. O timeout padrão de leitura do proxy dela é de 125 segundos, e só alguns planos permitem aumentá-lo.

Quais timeouts existem entre o navegador e o seu app?#

Uma requisição passa por várias etapas, e cada uma pode impor um limite:

  • Timeout de conexão: quanto o proxy espera para abrir uma conexão TCP com o app. O nginx usa 60 segundos por padrão (proxy_connect_timeout) e avisa que em geral ele não passa de 75 segundos.
  • Timeout de leitura: quanto o proxy espera entre duas leituras da resposta do app. O nginx usa 60 segundos por padrão (proxy_read_timeout). É um intervalo entre leituras, não um limite para a resposta inteira, então uma resposta em streaming que continua mandando bytes não o dispara.
  • Timeout de envio: a mesma ideia para escrever a requisição no app, 60 segundos por padrão.
  • Idle timeout: quanto tempo uma conexão pode ficar sem dados antes que o balanceador a feche. Um Application Load Balancer da AWS usa 60 segundos por padrão, configurável de 1 a 4000 segundos.
  • Keep-alive timeout: quanto tempo uma conexão já concluída fica aberta para reuso. Servidores http do Node.js usam 5000 ms por padrão.

A requisição legítima mais lenta precisa caber no menor limite do caminho. Um relatório que leva 90 segundos funciona sem nada na frente e falha atrás de um proxy de 60 segundos.

Como descobrir qual etapa estourou o tempo?#

Meça de fora e olhe o tempo decorrido. Timeout é um relógio, então o momento da falha o denuncia.

bash
curl -s -o /dev/null -w "status=%{http_code} connect=%{time_connect}s ttfb=%{time_starttransfer}s total=%{time_total}s\n" \
  https://example.com/slow-endpoint

Leia o resultado:

Falha depois de Culpado provável
~60 s Padrão do nginx ou do ALB (timeout de leitura ou idle)
~125 s Cloudflare (524 depois de 125 s por padrão); outras CDNs têm limites próprios
Na hora, ou em poucos ms Conexão recusada ou resetada: app fora do ar, porta errada, processo que caiu
Aleatório, a qualquer momento Keep-alive desalinhado ou um backend que às vezes morre

Depois confira o outro lado. A mesma URL é rápida quando você chama o app direto, sem passar pelo balanceador? Se sim, o app está bem e o limite está no caminho. Se também for lenta, o problema é o app, e aumentar timeouts só esconde isso. Os logs do app devem mostrar a requisição chegando e quanto o handler levou. Se a requisição nem aparece neles, ela morreu antes de chegar ao app.

Quais são as causas mais comuns e como corrigir?#

Da mais comum para a menos comum.

1. Um handler lento demais#

Um relatório, uma exportação, uma consulta lenta ou uma chamada a uma API externa leva mais tempo do que o proxy permite. Corrija nesta ordem:

  1. Encontre a parte lenta (plano da consulta, chamada externa, trabalho de CPU bloqueante).
  2. Deixe-a rápida: crie um índice, faça cache do resultado, tire o trabalho do event loop.
  3. Se ela é longa por natureza, devolva 202 Accepted com um ID de job na hora e deixe o cliente consultar o status ou receber um webhook.

Aumentar o timeout é o último recurso, e só empurra a parede para mais longe. Se fizer isso, mude em todas as etapas, não só numa:

nginx
location / {
    proxy_pass http://app;
    proxy_connect_timeout 5s;
    proxy_read_timeout    120s;
    proxy_send_timeout    120s;
}

Um timeout de conexão curto é uma boa ideia: se o app não está aceitando conexões, falhe em segundos em vez de esperar um minuto.

2. Keep-alive desalinhado (502 intermitente)#

Essa causa confunde muita gente porque o app está saudável. O balanceador mantém conexões abertas e as reaproveita. Se o app fecha uma conexão ociosa primeiro, o balanceador pode mandar uma requisição por uma conexão que já morreu e devolve 502. A AWS documenta isso: configure o idle timeout da aplicação maior que o do load balancer.

Servidores Node.js fecham conexões ociosas depois de 5 segundos por padrão, menos do que quase todo balanceador. Aumente:

js
import http from "node:http";

const server = http.createServer((req, res) => {
  res.end("ok");
});

// Maior que o idle timeout do load balancer (60s por padrão num ALB).
server.keepAliveTimeout = 65_000;
// Mantenha headersTimeout acima de keepAliveTimeout.
server.headersTimeout = 66_000;

server.listen(process.env.PORT ?? 3000);

Com nginx na frente do app, o ajuste equivalente é a diretiva keepalive_timeout dentro de upstream, que usa 60 segundos por padrão.

3. Backend fora do ar ou doente (503 e 502)#

Se o processo caiu, ficou sem memória ou está escutando na porta errada, o proxy recebe "connection refused" e responde 502 ou 503. Confira se o app escuta em 0.0.0.0 e na porta para a qual o proxy encaminha, não em 127.0.0.1 dentro de um container, e leia as últimas linhas do log em busca de um crash. Processos mortos por falta de memória estão em erros de falta de memória.

4. Event loop travado#

Um runtime de thread única que roda um loop síncrono longo para de responder tudo, inclusive os health checks. O balanceador vê timeouts em todas as rotas ao mesmo tempo. Leve o trabalho pesado de CPU para um worker ou uma fila.

5. Conexões longas#

WebSockets e server-sent events parecem ociosos entre uma mensagem e outra, então um idle timeout os derruba. Mande um ping ou uma linha de comentário com frequência maior que o menor idle timeout do caminho. Atenção: um ALB não conta frames PING do HTTP/2 como tráfego, então mande dados de verdade. Veja como hospedar um servidor WebSocket.

Como evitar esses erros?#

  • Documente os limites. Anote o timeout de cada etapa. Uma prática comum é ordená-los cliente > CDN > balanceador > proxy > app, para que cada camada interna desista antes da externa e o usuário veja a sua página de erro, não uma genérica.
  • Meça seus endpoints. Crie alertas pela latência p95 e p99, não pela média. Um p99 perto do timeout é um 504 a caminho.
  • Falhe rápido na conexão. Alguns segundos bastam para uma conexão local.
  • Torne o trabalho longo assíncrono, com um ID de job e um endpoint de status.
  • Mantenha vários backends. Com mais de um app saudável atrás de um load balancer, um crash não derruba o site. Só repita requisições que podem ser repetidas com segurança: um GET repetido é inofensivo, um POST repetido pode cobrar um cartão duas vezes.
  • Olhe a borda também. Uma CDN ou um proxy reverso na frente acrescenta o próprio limite, então teste pelo caminho completo, não só pela origem.

Como fazer isso na Square Cloud#

Na Square Cloud, um domínio personalizado pode ser servido por várias aplicações ao mesmo tempo. O tráfego é distribuído em round-robin com failover passivo (não há health check ativo): se uma aplicação para de responder, as requisições vão para outra do grupo. É o conselho de manter vários backends, sem você rodar um balanceador próprio.

Você faz o deploy das aplicações e coloca o mesmo domínio personalizado em cada uma. O número de aplicações por domínio depende do plano: até 2 no Standard, até 5 no Pro e até 10 no Enterprise.

Os containers ficam sempre ligados, e o reinício automático (ligado por padrão no deploy pelo painel) traz a aplicação de volta quando o processo encerra com erro. Se a memória estourar, a aplicação fica parada até você aumentar a RAM dela e iniciá-la de novo, então deixe uma folga. Os logs em tempo real (painel, CLI, VS Code e app de celular) mostram a saída do processo, erros incluídos. No Pro e no Enterprise, a latência p50/p95/p99 e os logs HTTP mostram quando um endpoint está chegando perto de um timeout.

O proxy na frente do app espera até 30 segundos para conectar na sua aplicação e não corta conexões longas: WebSocket e SSE passam sem buffer. O resto da lista continua valendo: mantenha os handlers rápidos, leve trabalho longo para um job e deixe o keep-alive timeout do app acima do idle timeout do que estiver na frente dele. Veja como funciona em load balancing.

Rede

Domínio próprio, TLS automático e CDN global para o seu app

Saiba mais

Perguntas frequentes

Qual a diferença entre 502, 503 e 504?

502 Bad Gateway: o proxy recebeu uma resposta inválida do upstream, ou a conexão caiu. 503 Service Unavailable: o servidor não consegue atender agora, por sobrecarga ou manutenção. 504 Gateway Timeout: o proxy não recebeu resposta do upstream a tempo.

Qual o timeout padrão do nginx e de um Application Load Balancer da AWS?

O nginx usa 60 segundos em proxy_connect_timeout, proxy_read_timeout e proxy_send_timeout. Um Application Load Balancer da AWS tem idle timeout de conexão de 60 segundos, configurável de 1 a 4000 segundos.

Por que recebo 502 intermitente mesmo com o app no ar?

Uma causa comum é o app fechar conexões keep-alive ociosas antes do load balancer. O balanceador então reaproveita uma conexão que já foi fechada. Deixe o keep-alive timeout do app maior que o idle timeout do balanceador.

Basta aumentar o timeout para resolver um 504?

Só como paliativo. Uma requisição que leva minutos deve virar um job em segundo plano que devolve um ID na hora, porque cada etapa (navegador, CDN, balanceador, proxy) tem o próprio limite e os clientes também desistem.

Próximo artigoO que é load balancer (balanceamento de carga)Load balancer é o componente que distribui requisições entre vários servidores. Veja como funciona, os algoritmos, as camadas 4 e 7 e um exemplo com nginx.

Mais em Rede e domínios

Ver todos os 27 artigos