Resolver o nginx 502 Bad Gateway: causas e soluções
502 Bad Gateway significa que o nginx não recebeu uma resposta válida do backend. As cinco causas mais frequentes, a linha certa no log de erros e como comprovar que a correção resultou mesmo.
O que significa realmente "502 Bad Gateway"
Um 502 não vem da sua aplicação, vem do nginx. O nginx aceitou o pedido, encaminhou-o para um backend (PHP-FPM, Node, Python, outro servidor web) e não recebeu de lá nenhuma resposta aproveitável. É por isso que a página de erro não mostra nada de útil.
Distinguir este código dos seus vizinhos poupa muito tempo numa emergência:
- 500 Internal Server Error: o backend respondeu, e a resposta foi um erro. A causa está no código da aplicação. Leia o log da aplicação, não o do nginx.
- 502 Bad Gateway: a ligação ao backend não chegou a estabelecer-se, ou interrompeu-se antes de existir uma resposta completa.
- 504 Gateway Time-out: a ligação estava de pé, o backend é que ficou demasiado tempo calado e o nginx perdeu a paciência.
Esta distinção é a alavanca mais importante nos casos de tempo limite excedido, porque a mesma página lenta aparece ora como 502 ora como 504, consoante quem desiste primeiro. Voltamos ao assunto mais abaixo.
A situação dos pacotes no Debian 13, Debian 12, Ubuntu 24.04 e Ubuntu 22.04
Perante erros 502, o nginx comporta-se da mesma forma nos quatro sistemas e as diretivas têm nomes idênticos. As diferenças estão quase todas do lado do PHP, e é precisamente daí que nasce a maioria dos 502 depois de uma mudança de distribuição.
| Sistema | nginx | PHP | Nome do serviço | Socket |
| Debian 13 (Trixie) | 1.26.3 | 8.4 | php8.4-fpm | /run/php/php8.4-fpm.sock |
| Debian 12 (Bookworm) | 1.22.1 | 8.2 | php8.2-fpm | /run/php/php8.2-fpm.sock |
| Ubuntu 24.04 LTS | 1.24.0 | 8.3 | php8.3-fpm | /run/php/php8.3-fpm.sock |
| Ubuntu 22.04 LTS | 1.18.0 | 8.1 | php8.1-fpm | /run/php/php8.1-fpm.sock |
Em todos os comandos seguintes, substitua o número de versão pelo do seu sistema. Todos os exemplos partem de uma shell root; caso contrário, coloque sudo à frente. Para saber que versão está instalada basta olhar para os binários do FPM, mesmo que o serviço nem sequer arranque:
ls /usr/sbin/php-fpm*
ls /etc/php/
Primeiro o log de erros: encontrar a linha certa
O erro mais comum no diagnóstico é procurar no log errado. O nginx tem um log de erros global e, muitas vezes, um log próprio por cada host virtual. Qual dos ficheiros vale está na configuração:
grep -Rn "error_log" /etc/nginx/nginx.conf /etc/nginx/sites-enabled/
Repare no -R maiúsculo. No Debian e no Ubuntu, em /etc/nginx/sites-enabled/ existem exclusivamente symlinks para sites-available, e o GNU grep, com o -r minúsculo, não segue nenhum symlink durante a descida recursiva. Com -rn obtém apenas as ocorrências de nginx.conf, enquanto a linha error_log do próprio vhost permanece invisível: precisamente aquela que interessa num caso de 502, porque o log global deixa de conter o erro FastCGI assim que o vhost redireciona o registo. Quem preferir ficar pelo -r faz grep diretamente aos diretórios de origem:
grep -rn "error_log" /etc/nginx/nginx.conf /etc/nginx/sites-available/ /etc/nginx/conf.d/
Sem indicação própria no bloco server, tudo vai parar a /var/log/nginx/error.log. O caminho mais fiável até à linha certa passa por acompanhar o log em direto: mantenha o log aberto num terminal, desencadeie o pedido num segundo terminal e observe as linhas que aparecem de novo.
tail -f /var/log/nginx/error.log
Em alternativa, filtre pela marca temporal. O nginx escreve a hora local no formato 2026/07/26 09:14:22, não em UTC. Comparar com um relógio noutro fuso horário falha com regularidade.
Uma linha de 502 segue sempre o mesmo padrão. Exemplo:
2026/07/26 09:14:22 [error] 812#812: *3 connect() to unix:/run/php/php8.2-fpm.sock
failed (2: No such file or directory) while connecting to upstream,
client: 203.0.113.7, server: example.com,
request: "GET /index.php HTTP/1.1",
upstream: "fastcgi://unix:/run/php/php8.2-fpm.sock:", host: "example.com"
Quatro elementos carregam toda a informação:
- A chamada de sistema:
connect(),recv(),send().connect()significa que nunca se chegou a estabelecer uma ligação.recv()significa que a ligação existia e que depois se interrompeu. - O número de erro entre parênteses, ver a tabela abaixo. É esse o verdadeiro diagnóstico.
- A fase:
while connecting to upstreampor oposição awhile reading response header from upstream. O primeiro é um problema de acessibilidade, o segundo um problema de tempo de execução ou de crash. - O campo
upstream:. Aí está o caminho ou o endereço que o nginx realmente usou. Não aquilo que supõe estar na configuração, mas aquilo que está de facto carregado.
| Mensagem | Significado | Secção |
| 2: No such file or directory | O ficheiro de socket não existe | Serviço parado ou caminho errado |
| 13: Permission denied | O socket existe, mas o nginx não lhe pode aceder | Permissões |
| 111: Connection refused | Nada está à escuta no endereço e na porta | Backend inacessível |
| 110: Connection timed out | Sem resposta dentro do prazo | Tempo limite excedido |
| 104: Connection reset by peer | O processo do backend morreu a meio do pedido | Crashes e limites |
| 11: Resource temporarily unavailable | Fila de espera do socket cheia | Crashes e limites |
No nginx, a linha relativa a 13: Permission denied traz muitas vezes o nível [crit] em vez de [error]. Quem filtra apenas por [error] não dá por ela. Filtre antes pelo texto:
grep -n "upstream" /var/log/nginx/error.log
A segunda metade da verdade está no log do PHP-FPM, por predefinição em /var/log/php8.2-fpm.log. Em casos de crash e de limites atingidos é aí que está a justificação, ao passo que o nginx apenas vê o sintoma.
Causa 1: o PHP-FPM não está a correr
O clássico número de erro 2. Verifique primeiro o estado do serviço:
systemctl is-active php8.2-fpm
systemctl status php8.2-fpm --no-pager -l
O is-active responde com uma única palavra, o que chega para um script. Se surgir inactive ou failed, vá buscar o motivo ao journal, e de preferência com uma janela temporal em vez das últimas dez linhas:
journalctl -u php8.2-fpm --since "30 min ago" --no-pager
Muitas vezes a causa é uma configuração de pool danificada que ficou para trás depois de um reload. O FPM tem um teste de sintaxe próprio, que corre sem reiniciar o serviço:
php-fpm8.2 -t
Erros de arranque típicos, na sua formulação literal, e o que significam:
ERROR: [pool www] cannot get uid for user 'webuser': o utilizador de sistema indicado emuser =já não existe, por exemplo depois de uma migração.ERROR: unable to bind listening socket for address '/run/php/php8.2-fpm.sock': No such file or directory (2): falta o diretório/run/php. Ele fica num tmpfs e é criado no arranque do serviço. Quem apontar olistenpara um caminho fora dele tem de tratar da criação do diretório.ERROR: An another FPM instance seems to already listen on ...: um processo sobrante de um reinício falhado continua agarrado ao socket.
Como reconhecer que está mesmo resolvido: não é pelo facto de o systemctl restart ter corrido sem produzir saída. Um master do FPM arranca mesmo quando nenhum processo de trabalho consegue aceitar pedidos. O que conta é que o socket esteja visível no sistema e que o FPM responda nele.
ss -lx | grep php
Para o verdadeiro teste de resposta, ative em /etc/php/8.2/fpm/pool.d/www.conf a linha ping.path = /ping, recarregue o FPM e interrogue o socket diretamente, sem passar de todo pelo nginx:
apt-get install -y libfcgi-bin
SCRIPT_NAME=/ping SCRIPT_FILENAME=/ping REQUEST_METHOD=GET \
cgi-fcgi -bind -connect /run/php/php8.2-fpm.sock
Se vier pong de volta, o lado do PHP está em ordem e o erro situa-se entre o nginx e o socket. Se não vier nada, nem vale a pena continuar a procurar no nginx.
Causa 2: caminho de socket errado
No Debian e no Ubuntu esta é, de longe, a causa mais frequente: o socket leva a versão do PHP no nome, a configuração do nginx fixa esse nome de forma rígida, e uma atualização de distribuição separa os dois.
Em concreto: uma atualização de Debian 12 para Debian 13 leva o PHP de 8.2 para 8.4. O socket antigo /run/php/php8.2-fpm.sock desaparece, mas continua escrito no ficheiro do vhost. O resultado é um 502 em cada uma das páginas PHP, logo a seguir ao reinício. O mesmo acontece na passagem do Ubuntu 22.04 para 24.04 (8.1 para 8.3).
Uma segunda armadilha está na configuração de exemplo que vem incluída. Em /etc/nginx/sites-available/default existe um bloco comentado cujo fastcgi_pass aponta para uma versão de PHP que há anos deixou de ser atual. Quem se limita a descomentar essas linhas acaba de criar o 502.
Compare os dois lados. O que o nginx quer usar:
grep -Rn "fastcgi_pass" /etc/nginx/
O que o FPM oferece de facto:
grep -n "^listen *=" /etc/php/*/fpm/pool.d/*.conf
O acréscimo *= no padrão de pesquisa é intencional: exige, depois de listen, um número qualquer de espaços e a seguir um sinal de igual. Um simples ^listen apanharia também listen.owner, listen.group e listen.mode, e a linha procurada perder-se-ia no meio das ocorrências. Já o caractere universal /etc/php/*/ está correto assim, pois cobre todas as versões de PHP instaladas. E o que existe no sistema em execução:
ls -l /run/php/
As três saídas têm de mostrar o mesmo caminho. Importante: use grep -R sobre /etc/nginx/ e não apenas sobre o ficheiro de que desconfia. Os fragmentos integrados através de include são um esconderijo apreciado, tal como ficheiros antigos em sites-available que continuam ativos por causa de um symlink esquecido em sites-enabled. Também aqui vale a regra: só o -R maiúsculo segue esses symlinks e lhe mostra, assim, que ficheiro está realmente ativo.
ls -l /etc/nginx/sites-enabled/
Antes de alterar seja o que for, faça uma cópia. Custa dois segundos e, na dúvida, poupa-lhe um restauro a partir do backup:
mkdir -p /root/backups
cp -a /etc/nginx/sites-available/default /root/backups/default.bak
Quem intervém várias vezes faz melhor em acrescentar uma marca temporal (default.bak.$(date +%F-%H%M)), porque o cp -a sobrescreve sem aviso um .bak já existente.
Depois da correção, teste sempre primeiro e só depois recarregue. Use reload em vez de restart, para que as ligações existentes não caiam:
nginx -t
systemctl reload nginx
Atenção: o nginx -t verifica exclusivamente a sintaxe. Um caminho de socket que não existe conta como configuração perfeitamente válida. Um syntax is ok a verde não é, por isso, prova de que o 502 desapareceu.
Causa 3: permissões do socket
Número de erro 13. O socket está lá, o nginx é que não o pode abrir. No Debian e no Ubuntu, o nginx corre com o utilizador www-data, e o pool predefinido do FPM cria o socket em conformidade. Isso vê-se no ficheiro do pool:
grep -n "listen.owner\|listen.group\|listen.mode" /etc/php/*/fpm/pool.d/*.conf
Com listen.owner = www-data, listen.group = www-data e o modo 0660, a articulação funciona sem qualquer intervenção. As coisas podem correr mal em três situações:
- Um pool próprio por projeto. Se
useregroupforem definidos para um utilizador do projeto, olisten.grouptem ainda assim de ser um grupo a que o nginx pertença. O habitual élisten.owner = utilizadorprojetoem conjunto comlisten.group = www-data. - Socket fora de /run. Não basta que o ficheiro de socket esteja acessível: cada diretório no caminho até lá precisa da permissão de execução para o nginx. Um socket numa pasta pessoal com o modo 0700 não está acessível a ninguém a não ser ao proprietário.
- nginx com o
useralterado em/etc/nginx/nginx.conf.
Confirme a suspeita sem adivinhar, tentando o acesso exatamente com o utilizador que dele precisa em produção:
id www-data
sudo -u www-data test -w /run/php/php8.2-fpm.sock && echo "acesso disponível" || echo "sem acesso"
Ainda mais conclusiva é a chamada cgi-fcgi da secção anterior, igualmente precedida de sudo -u www-data. Se o FPM responder como root mas não como www-data, o diagnóstico é inequívoco.
Defina os valores no ficheiro do pool, não com chmod sobre o ficheiro de socket. Um chmod 666 dura exatamente até ao reinício seguinte do FPM: nessa altura o FPM volta a criar o socket com as permissões configuradas e o erro regressa, normalmente no pior momento possível.
No Debian e no Ubuntu o AppArmor está ativo. O perfil para o nginx que vem incluído não é imposto de origem, mas pode ter sido ativado por modelos de hardening. Se uma mensagem 13 persistir apesar de as permissões estarem corretas, vale a pena consultar aa-status e journalctl -k | grep DENIED.
Causa 4: tempo limite excedido em pedidos longos
É aqui que um método rigoroso se separa da adivinhação, porque o simples esgotar do prazo do nginx produz um 504, não um 502. Quando um pedido longo termina em 502, foi quase sempre o PHP-FPM que eliminou antes o processo de trabalho, e o nginx apenas viu uma ligação cortada. No log aparece então, tipicamente:
recv() failed (104: Connection reset by peer) while reading response header from upstream
Três prazos atuam em simultâneo, e é a ordem entre eles que decide o código de estado:
max_execution_timenophp.ini, 30 segundos por predefinição em modo FPM. Conta apenas o tempo de execução do script. O tempo de espera em chamadas de sistema, por exemplo numa consulta à base de dados que ficou pendurada, não conta no Linux. É por isso que este valor não salva ninguém precisamente no problema em que toda a gente espera que salve.request_terminate_timeoutno ficheiro do pool, desativado de origem. Termina o processo de trabalho de forma abrupta, seja qual for a razão por que está pendurado. É este o valor que produz os 502.fastcgi_read_timeoutno nginx, 60 segundos por predefinição. Quando se esgota, o resultado é um 504.
A ordem útil é crescente de dentro para fora, para que atue primeiro a camada que ainda consegue produzir uma mensagem de erro compreensível. Por exemplo 60, depois 75, depois 90 segundos. Ordenados ao contrário, o que obtém são 502 em vez de erros de PHP legíveis.
grep -rn "request_terminate_timeout" /etc/php/*/fpm/pool.d/*.conf
Aquilo que o FPM eliminou fica registado em texto claro no log dele:
WARNING: [pool www] child 1234, script '/var/www/html/import.php'
(request: "POST /import.php") execution timed out (76.271849 sec), terminating
Antes de aumentar prazos, descubra para onde vai o tempo. O FPM traz para isso um log próprio, que escreve uma pilha de chamadas PHP completa quando o limite é ultrapassado. Ative-o no ficheiro do pool:
slowlog = /var/log/php-fpm-slow.log
request_slowlog_timeout = 5s
Depois de um systemctl reload php8.2-fpm, no pedido lento seguinte fica ali registada a função, com número de linha, que está pendurada. Na prática, em quatro de cada cinco casos trata-se de uma consulta à base de dados sem índice ou de uma chamada a uma interface de programação externa sem prazo próprio. Aumentar os prazos apenas prolonga o tempo até ao erro e bloqueia ainda mais processos de trabalho.
Causa 5: backend inacessível
Afeta qualquer backend contactado por TCP: o FPM na porta 9000, uma aplicação Node, um serviço Java, um container. A mensagem indicadora é o número de erro 111.
connect() to 127.0.0.1:3000 failed (111: Connection refused) while connecting to upstream
Verifique primeiro se há sequer alguma coisa à escuta e, sobretudo, em quê:
ss -ltnp
Este comando lista exclusivamente sockets TCP, e daí nasce um equívoco muito comum: um pool de PHP-FPM na configuração de origem escuta num socket Unix em /run/php/ e não aparece de todo nesta lista, ainda que esteja a funcionar sem falhas. Só assim se torna visível:
ss -lxn | grep php-fpm
ls -l /run/php/
Para o FPM, o ss -ltnp só é conclusivo, portanto, se o pool tiver sido deliberadamente passado para TCP com listen = 127.0.0.1:9000. Já para backends em Node, Java ou containers é exatamente o comando certo.
Três armadilhas que raramente constam dos guias:
- O localhost resolve primeiro para ::1. Se no nginx estiver
proxy_pass http://localhost:3000;mas a aplicação só escutar em127.0.0.1, o nginx tenta o endereço IPv6 e recebe "Connection refused". O serviço corre, a porta está aberta e mesmo assim há 502. Solução: escrever127.0.0.1por extenso no nginx, ou ligar a aplicação a ambas as famílias de endereços. - O nginx resolve os nomes uma única vez, no carregamento. Se no
proxy_passestiver um nome de máquina, o nginx guarda o endereço. Se o backend mudar de endereço IP, por exemplo um container reiniciado, os pedidos ficam a bater no vazio até aoreloadseguinte. - Firewall no caminho de volta. Com um backend noutro servidor aparece
113: No route to hostou um tempo limite excedido, em vez de "Connection refused". Verifique comufw statuse com um teste de ligação direto a partir do servidor nginx.
Se for usado um bloco upstream com vários destinos, junta-se-lhe uma mensagem própria:
no live upstreams while connecting to upstream
Significa que o nginx retirou de circulação todos os destinos durante fail_timeout, depois de tentativas falhadas repetidas. Mesmo depois de reparar o backend, é preciso esperar que esse prazo termine para que os pedidos voltem a passar. Um systemctl reload nginx repõe o estado de imediato.
O 502 que não encaixa em nenhuma das cinco causas
Dois casos parecem uma avaria sem o serem, e por isso custam muito mais tempo do que a média.
Cabeçalho de resposta demasiado grande. A aplicação corre sem falhas, apenas alguns pedidos devolvem 502:
upstream sent too big header while reading response header from upstream
A origem está em cookies grandes ou em atributos de sessão nos cabeçalhos: o buffer do nginx é pequeno demais. No bloco server ou location:
fastcgi_buffer_size 32k;
fastcgi_buffers 8 16k;
fastcgi_busy_buffers_size 64k;
Com proxy_pass, as diretivas chamam-se proxy_buffer_size e proxy_buffers. É típico que só os utilizadores com sessão iniciada sejam afetados, enquanto a página inicial carrega sem problemas.
Processos de trabalho esgotados. Sob carga, no log do FPM aparece:
WARNING: [pool www] server reached pm.max_children setting (5), consider raising it
Os novos pedidos ficam então na fila de espera do socket. Se também essa encher, o nginx comunica 11: Resource temporarily unavailable. Antes de aumentar o pm.max_children, faça uma conta rápida: memória disponível a dividir pelo consumo real de um processo de trabalho. Um valor demasiado alto troca os 502 por um estado do sistema em que a memória se esgota, e isso atinge também a base de dados.
Crashes. Linhas com exited on signal 11 (SIGSEGV) apontam para uma extensão de PHP defeituosa, muitas vezes depois de uma mudança de versão do PHP com módulos sobrantes da versão anterior.
Quando a intervenção não resolve: o caminho de volta
Duas regras mantêm os estragos pequenos. Primeira: uma alteração de cada vez, com cópia do ficheiro original em /root/backups, nunca no diretório web. Segunda: verificar depois de cada passo, em vez de mudar três coisas ao mesmo tempo e ficar depois sem saber qual delas ajudou.
Se o nginx deixar de funcionar por completo depois de uma alteração, reponha a cópia e recarregue:
cp -a /root/backups/default.bak /etc/nginx/sites-available/default
nginx -t
systemctl reload nginx
Se o nginx deixar de arrancar após um restart, o systemctl status nginx raramente diz o suficiente. Mais conclusivo:
journalctl -u nginx --since "10 min ago" --no-pager
O motivo mais frequente para um restart falhado com uma configuração sintaticamente impecável é a porta 80 ou 443 estar ocupada, normalmente por um processo da execução anterior. O ss -ltnp | grep ':80' mostra o culpado.
Como saber que o problema está realmente resolvido
Um comando sem mensagem de erro não prova rigorosamente nada. O systemctl reload também fica calado quando, na prática, nada mudou, e o nginx -t só verifica sintaxe. Sólidas são estas quatro provas:
- Consultar o código de estado diretamente no servidor, para que nem uma cache nem um serviço a montante falseiem o resultado:
O esperado é 200, não 502. Com vários hosts virtuais, indique o nome:curl -sS -o /dev/null -w "%{http_code}\n" http://127.0.0.1/curl -H "Host: example.com" ... - O log de erros mantém-se em silêncio. Esvazie-o antes do teste com
truncate -s 0 /var/log/nginx/error.log, desencadeie vários pedidos e volte a consultá-lo. Um ficheiro vazio é que é a verdadeira prova. - O FPM responde no socket, sem passar pelo nginx, através de
cgi-fcgie comowww-data. Assim resolve-se num só passo a questão das permissões e a do caminho. - Um reinício não muda nada. É o ponto mais importante e o mais vezes saltado. Muitas medidas imediatas (permissões definidas à mão, diretórios criados manualmente em
/run, um serviço iniciado mas não ativado) não sobrevivem a um reinício. Verifique osystemctl is-enabled php8.2-fpm nginxe reinicie o servidor uma vez de forma controlada, enquanto ainda está a acompanhar, em vez de deixar isso para a próxima janela de manutenção.
Nos servidores root KVM e nos servidores dedicados da KernelHost, este reinício faz-se na área de cliente, com acesso à consola incluído, mesmo quando o serviço web está inacessível nesse momento. Os servidores estão no datacenter maincubes em Frankfurt am Main (TÜV TIER3+), ligados a uma rede própria com proteção DDoS. Para aprofundar: resolver o nginx 504 Gateway Time-out e calcular corretamente o pm.max_children do PHP-FPM.
Lista de verificação rápida para a emergência
tail -f /var/log/nginx/error.log, desencadear o pedido, anotar o número de erro.- Número 2 ou 111: o serviço está a correr? o caminho está certo? Compare
ss -lx | grep phpcomgrep -Rn "fastcgi_pass" /etc/nginx/. - Número 13: permissões no ficheiro do pool, não via
chmod. - Número 104 ou 110: ler o log do FPM e o
slowlog, e só depois falar de prazos. - Mensagem "too big header": aumentar os tamanhos dos buffers.
- Depois da correção: esvaziar o log, voltar a testar, reiniciar o servidor uma vez.
Perguntas frequentes
Porque é que vejo um 502 e não um 504, se a página apenas está lenta?
Depois da atualização para o Debian 13, todas as páginas PHP dão 502. O que tenho de mudar?
O 'nginx -t' chega como prova de que o erro está corrigido?
Como encontro no log de erros a linha que corresponde ao meu 502?
O 502 só acontece com utilizadores com sessão iniciada, a página inicial carrega normalmente. A que se deve?
Corrigi as permissões do socket com chmod, mas depois de um reinício o erro voltou. Porquê?
2026 KernelHost GmbH. Todos os direitos reservados. Este guia está protegido por direitos de autor. A sua republicação noutros sites, na íntegra, em parte ou de forma editada, não é permitida sem o nosso consentimento por escrito. Citações com indicação da fonte e ligação são expressamente bem-vindas.

