Resolver o nginx 504 Gateway Time-out: encontrar a causa em vez de aumentar o prazo
Num 504 o backend estava acessível, apenas respondeu demasiado devagar. Como determinar pelo log de tempos onde o tempo é consumido, qual dos muitos prazos é que realmente se aplica e porque é que aumentar o prazo costuma apenas adiar a avaria.
Um 504 Gateway Time-out é a mais paciente de todas as páginas de erro. O nginx aceitou o pedido, encaminhou-o para o backend e depois esperou até que expirasse um prazo definido internamente. O backend esteve acessível o tempo todo, apenas não respondeu a tempo. É precisamente aí que está a diferença face ao código vizinho: no 502 Bad Gateway o backend responde mal ou não responde de todo, no 504 responde demasiado devagar. Este guia mostra como medir onde o tempo é realmente consumido, qual dos muitos limites de tempo é que de facto se aplica e porque é que aumentar esse limite é quase sempre a pior das respostas disponíveis.
Todas as indicações se referem ao Debian 13 (trixie), Debian 12 (bookworm), Ubuntu 24.04 LTS e Ubuntu 22.04 LTS. Os comandos estão escritos para correrem como root, como utilizador normal coloque sudo à frente. Nos exemplos aparece o PHP 8.4, substitua o número de versão pelo do seu sistema:
| Sistema | PHP | Serviço | Configuração |
|---|---|---|---|
| Debian 13 (trixie) | 8.4 | php8.4-fpm | /etc/php/8.4/fpm/ |
| Debian 12 (bookworm) | 8.2 | php8.2-fpm | /etc/php/8.2/fpm/ |
| Ubuntu 24.04 LTS | 8.3 | php8.3-fpm | /etc/php/8.3/fpm/ |
| Ubuntu 22.04 LTS | 8.1 | php8.1-fpm | /etc/php/8.1/fpm/ |
ls /etc/php/
As diretivas do nginx têm o mesmo nome nos quatro sistemas. As diferenças estão do lado do PHP e da base de dados, e ficam assinaladas nos pontos correspondentes.
Quem desistiu primeiro determina onde procurar
Antes de abrir um ficheiro, responda a uma pergunta: qual foi a camada que desistiu? O código de estado já o revela.
| Código | O que aconteceu | Onde procurar |
|---|---|---|
| 500 Internal Server Error | O backend respondeu, e a resposta foi um erro | Log da aplicação |
| 502 Bad Gateway | A ligação não chegou a estabelecer-se ou interrompeu-se | Serviço, socket, permissões, crashes |
| 504 Gateway Time-out | A ligação estava de pé, a resposta não chegou dentro do prazo | Tempo de execução no backend |
| 408 Request Timeout | O visitante não terminou de enviar a tempo o seu próprio pedido | Uploads, ligações lentas |
| 499 (só no log) | O visitante desistiu antes de o nginx terminar | Demasiado lento, mas ainda dentro do prazo |
A linha do 499 é a mais subestimada de todas. Não é um erro, é um sistema de aviso prévio: o visitante fechou o separador porque a página demorava demasiado. Se um 504 desaparece depois de aumentar o prazo e no lugar dele surgem 499, não ficou nada resolvido.
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head
O campo 9 vale para o formato predefinido combined. Muitos 504 com poucos 499 apontam para páginas pesadas isoladas, o quadro inverso aponta para uma aplicação lenta de ponta a ponta.
Antes de mudar seja o que for: o caminho de volta
O diagnóstico das duas secções seguintes é puramente de leitura. O risco só começa quando mexe nas configurações, e isso pode parar o serviço por três vias: uma configuração do nginx com erros impede o arranque do servidor web, um ficheiro de pool com erros impede o arranque do PHP-FPM, e limites aumentados com demasiada generosidade podem esgotar a memória RAM. O último caso é o mais desagradável, porque o kernel passa a terminar processos e não é forçosamente o culpado que apanha. Se calhar ao serviço SSH, o servidor deixa de poder ser operado pela rede.
Comece, por isso, por criar cópias, debaixo de /root e nunca no diretório web. A marca temporal no nome é importante, porque raramente se intervém uma só vez e o cp -a sobrepõe-se a uma cópia existente sem dizer nada:
mkdir -p /root/backups
cp -a /etc/nginx/nginx.conf /root/backups/nginx.conf.$(date +%F-%H%M)
cp -a /etc/nginx/sites-available/example.com /root/backups/example.com.$(date +%F-%H%M)
cp -a /etc/php/8.4/fpm/php.ini /root/backups/php.ini.$(date +%F-%H%M)
cp -a /etc/php/8.4/fpm/pool.d/www.conf /root/backups/www.conf.$(date +%F-%H%M)
O caminho de volta são três linhas, e a ordem é propositada:
cp -a /root/backups/example.com.2026-09-03-1030 /etc/nginx/sites-available/example.com
nginx -t
systemctl reload nginx
Use reload em vez de restart enquanto for possível. O reload só adota a nova configuração se ela estiver sem erros. Um restart termina primeiro o processo em execução e, havendo um erro, deixa-o sem servidor web.
Se o servidor deixar de responder por completo, abra a consola VNC na área de cliente, nos servidores root KVM e nos servidores dedicados da KernelHost. Ela está ligada à camada de virtualização, ou à própria ligação física, e não à pilha de rede do sistema convidado, por isso continua a funcionar mesmo quando já não há nenhum serviço acessível. Autentique-se lá uma vez antes disso e certifique-se de que sabe a palavra-passe de root. Comando de verificação depois de cada intervenção:
systemctl is-active nginx php8.4-fpm
free -m
A linha do log de erros que decide o caso
Um 504 deixa sempre rasto:
2026/09/03 10:12:33 [error] 812#812: *5 upstream timed out (110: Connection timed out)
while reading response header from upstream, client: 203.0.113.7, server: example.com,
request: "GET /report.php HTTP/1.1", upstream: "fastcgi://unix:/run/php/php8.4-fpm.sock:"
O que decide não é o número de erro 110, que é igual em todos os 504, mas sim a fase que vem a seguir:
while connecting to upstream: a ligação não chegou a estabelecer-se. Num backend remoto é quase sempre um filtro de pacotes que descarta os pacotes em vez de os rejeitar, porque uma rejeição voltaria de imediato e daria um 502.while sending request to upstream: o nginx não conseguiu despachar o corpo do pedido, típico em uploads grandes.while reading response header from upstream: o caso normal. O backend recebeu tudo e está a processar, sem enviar sequer o primeiro cabeçalho.while reading upstream: os cabeçalhos chegaram e depois o corpo travou. É o que vê em respostas de streaming e em exportações.
grep -n "upstream timed out" /var/log/nginx/error.log | tail -20
Muitos hosts virtuais escrevem num log de erros próprio. Onde fica esse ficheiro e porque é que a procura em sites-enabled precisa do -R maiúsculo está explicado no artigo sobre o 502. Se a procura não devolver nada apesar de o browser mostrar um 504, o erro não vem deste nginx.
Onde o tempo é consumido: medir em vez de adivinhar
O nginx consegue registar, pedido a pedido, quanto tempo o backend demorou. Este é o passo mais importante do diagnóstico, porque responde sem suposições à pergunta "aplicação ou linha". No bloco http de /etc/nginx/nginx.conf:
log_format kh_timing '$time_iso8601 $status rt=$request_time '
'uct=$upstream_connect_time uht=$upstream_header_time '
'urt=$upstream_response_time "$request"';
No bloco server afetado, uma segunda linha de registo. O log de acessos existente fica intacto, o nginx escreve os dois:
access_log /var/log/nginx/timing.log kh_timing;
nginx -t
systemctl reload nginx
tail -n 5 /var/log/nginx/timing.log
Ao fim de alguns minutos de funcionamento, traga os pedidos mais lentos para cima:
awk '{ t=$3; sub(/^rt=/, "", t); print t, $0 }' /var/log/nginx/timing.log | sort -rn | head -20
| Observação | Interpretação | Passo seguinte |
|---|---|---|
| uct alto com backend local | O estabelecimento da ligação está a emperrar | Fila de espera do socket cheia, resolução de nomes lenta |
| uht e urt quase iguais, ambos altos | O backend processa antes de enviar o primeiro cabeçalho | Aplicação, base de dados, interface externa |
| uht baixo, urt alto | O cabeçalho chegou depressa, o corpo vem a conta-gotas | Streaming, exportações, ciclos sobre muitos registos |
| urt baixo, rt alto | O backend foi rápido, o tempo perdeu-se depois | Linha do visitante, resposta muito grande |
| Hífen em vez de número | Não houve nenhum backend envolvido | Ficheiro estático ou desistência antes do encaminhamento |
Há dois pormenores que poupam muito tempo. Vários valores separados por vírgula num mesmo campo significam que o pedido foi para mais do que um destino, ou seja, houve uma repetição. E a pista mais forte de todas: se a duração medida corresponder ao segundo ao valor configurado, por exemplo 60,001 segundos com um prazo de 60, então foi o prazo que atuou e não o backend que desistiu por si. Valores irregulares como 43,7 segundos mostram que houve outra coisa a travar.
Qual é o limite de tempo que realmente se aplica
O nginx tem mais de uma dúzia de diretivas para tempos limite, e a hora mais frequentemente desperdiçada nasce de alguém mexer na diretiva errada. Qual delas se aplica depende do módulo que trata do bloco location.
| Diretiva | Predefinição | Aplica-se em blocos com | Efeito ao expirar |
|---|---|---|---|
| proxy_connect_timeout | 60s | proxy_pass | 504, "while connecting to upstream" |
| proxy_send_timeout | 60s | proxy_pass | 504, "while sending request to upstream" |
| proxy_read_timeout | 60s | proxy_pass | 504, o prazo decisivo em backends por proxy |
| fastcgi_connect_timeout | 60s | fastcgi_pass | 504, como acima, para o PHP-FPM |
| fastcgi_send_timeout | 60s | fastcgi_pass | 504, como acima |
| fastcgi_read_timeout | 60s | fastcgi_pass | 504, o prazo decisivo com PHP |
| send_timeout | 60s | em todo o lado | não dá 504, fecha-se a ligação ao visitante |
| client_body_timeout | 60s | em todo o lado | 408, não 504 |
O prazo aplica-se entre duas leituras, não à resposta inteira. Um download que dure dez minutos e entregue dados sem interrupção passa sem problemas. Um backend que fique 61 segundos calado vai fora. Em exportações que travam, ajuda por isso mais fazer a aplicação produzir alguma saída com regularidade do que aumentar o prazo.
send_timeout não faz nada contra um 504. Este prazo diz respeito à transmissão para o visitante. Se expirar, não há página de erro, há um download interrompido. Só se torna relevante quando desliga o buffering com proxy_buffering off;, porque a partir daí um visitante lento trava tudo até ao backend.
O nginx aceita também diretivas que não têm qualquer efeito no bloco em causa. Um proxy_read_timeout 300s; num bloco PHP com fastcgi_pass está sintaticamente correto, o nginx -t responde syntax is ok, e a página continua a falhar ao fim de 60 segundos. Ao contrário acontece o mesmo. É esta a causa mais frequente de um prazo aumentado não surtir efeito nenhum. O que vale de facto vê-se na configuração já reunida:
nginx -T | grep -E "read_timeout|send_timeout|fastcgi_pass|proxy_pass"
Se um caminho isolado tiver mesmo de correr durante mais tempo, defina o prazo exatamente aí e em mais lado nenhum. O sinal de igual transforma-o numa correspondência exata, e essa ganha ao bloco geral location ~ \.php$, que trata dos restantes ficheiros PHP:
location = /admin/export.php {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
fastcgi_read_timeout 300s;
}
nginx -t
systemctl reload nginx
curl -s -o /dev/null -w "%{http_code} %{time_total}\n" -H "Host: example.com" http://127.0.0.1/admin/export.php
PHP: porque é que o max_execution_time raramente se aplica aqui
A suposição imediata é que o PHP termina por si um script encravado. Neste caso concreto, isso quase nunca é verdade. Primeiro, a armadilha da medição: o php -i consulta a variante de linha de comandos. Essa usa uma configuração própria em /etc/php/8.4/cli/ e corre de qualquer forma sem limite de tempo de execução, por isso não diz nada sobre o FPM. Determinantes são estes dois sítios:
grep -n "^max_execution_time" /etc/php/8.4/fpm/php.ini
grep -rn "max_execution_time" /etc/php/8.4/fpm/pool.d/
No ficheiro do pool, o valor pode estar sobreposto com php_value[max_execution_time] ou php_admin_value[max_execution_time]. Um valor definido com php_admin_value já não pode ser alterado a partir da aplicação com ini_set(). Se a sua framework aumenta o tempo de execução por conta própria e isso deixou de repente de ter efeito, a razão é esta.
E agora o ponto central: no Linux, o tempo de espera dentro de chamadas de sistema não conta. O relógio só anda enquanto o próprio script está a processar. Se ficar à espera de uma consulta à base de dados, de uma interface externa ou do sistema de ficheiros, o relógio para. Assim, um script pode ficar dez minutos agarrado a uma consulta encravada sem que o limite de tempo de execução chegue alguma vez a atuar. Quem o termina é o prazo do nginx, e o resultado é o 504.
Daí decorre uma regra incómoda: o max_execution_time protege de ciclos infinitos no código próprio, não da espera. O único limite rígido do lado do PHP é o request_terminate_timeout no ficheiro do pool, que arruma o processo de trabalho independentemente daquilo em que ele está preso. Só que produz um 502 e não um 504. Ordene os prazos de forma crescente, de dentro para fora, para que atue primeiro a camada que ainda consegue produzir uma mensagem compreensível. Com scripts que processam isto funciona, com scripts que esperam não funciona, pela razão que acabámos de referir. Aí só resta limitar o próprio tempo de espera.
Porque é que aumentar o prazo costuma ser a resposta errada
Faça as contas connosco. Um pool com pm.max_children = 10 tem dez processos de trabalho. Uma página demora 90 segundos. Dez chamadas em simultâneo ocupam assim, durante minuto e meio, cada um deles. Nesse período ninguém recebe mais nenhuma página PHP, nem sequer a página inicial. Uma subpágina lenta transformou-se numa avaria. Há três mecanismos que agravam isto:
- O visitante recarrega a página. Isso gera um pedido adicional, mas não liberta o antigo. O PHP só dá pela desistência de um visitante quando o script produzir a próxima saída, e um script que está a processar fica muito tempo sem produzir nada.
- O nginx repete por iniciativa própria. Num bloco
upstreamcom vários destinos, oproxy_next_upstreamestá por predefinição emerror timeout. Um pedido que expirou segue para o servidor seguinte e a consulta cara corre uma segunda vez. Os pedidos de escrita ficam de fora, os de leitura não. Desliga-se comproxy_next_upstream error;. - A monitorização repete igualmente. Um intervalo de verificação de 60 segundos sobre uma página que precisa de 90 segundos gera uma carga permanente que nunca chega a ser escoada.
A isto acresce que ninguém espera cinco minutos por uma página web. Um prazo de 300 segundos transforma um problema de um minuto num problema de cinco minutos, e o visitante já foi embora há muito enquanto o processo de trabalho continua a processar.
O grau real de ocupação mostra-o a página de estado do PHP-FPM. Defina pm.status_path = /fpm-status no ficheiro do pool e crie no bloco server um acesso alcançável apenas localmente:
location = /fpm-status {
allow 127.0.0.1;
deny all;
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
}
systemctl reload php8.4-fpm
nginx -t
systemctl reload nginx
curl -s -H "Host: example.com" http://127.0.0.1/fpm-status
Repare em active processes, listen queue e max active processes. Se o listen queue se mantiver permanentemente acima de zero, o número de processos de trabalho não chega para o tempo de execução atual das páginas. É esse o momento em que tem de baixar o tempo de execução, não de aumentar o prazo.
Os três consumidores de tempo habituais
Base de dados
Na maioria dos casos, é aqui que o tempo se esconde. Comece por ver o que está a correr neste momento. Nos quatro sistemas, o acesso root passa por predefinição pelo socket Unix, por isso o comando dispensa palavra-passe:
mysql -e "SHOW FULL PROCESSLIST;"
As colunas interessantes são Time e State. Valores como Sending data ou Waiting for table metadata lock com segundos de dois dígitos são o seu caso. Para uma procura sistemática, use o log de consultas lentas, que se pode ligar com o serviço a correr:
mysql -e "SET GLOBAL slow_query_log = 1; SET GLOBAL long_query_time = 1;"
mysql -e "SHOW VARIABLES LIKE 'slow_query_log_file';"
O nome do ficheiro lê-se na segunda saída, porque ele varia: o Debian aposta habitualmente no MariaDB e escreve em /var/log/mysql/mariadb-slow.log, no Ubuntu o ficheiro tem outro nome consoante o servidor instalado. Ao fim de alguns minutos, avalie e volte a desligar, porque o log custa carga de escrita:
mysqldumpslow -s t /var/log/mysql/mariadb-slow.log | head -30
mysql -e "SET GLOBAL slow_query_log = 0;"
A opção -s t ordena por tempo total. A consulta mais cara verifica-se com EXPLAIN, e costuma faltar um índice exatamente na coluna pela qual se filtra ou ordena. O SET GLOBAL atua de imediato, mas não sobrevive a um reinício da base de dados. Também do lado da base de dados existe um limite máximo por consulta: o MariaDB tem o max_statement_time em segundos, o MySQL o max_execution_time em milissegundos, este último apenas para consultas de leitura. Assim, a sua aplicação recebe um erro limpo em vez de um processo de trabalho ocupado.
Interfaces externas
Se a sua página consultar um prestador de pagamentos ou um servidor de licenças a cada chamada, a avaria deles passa a ser o seu 504. Meça essa chamada em separado, a partir do servidor:
curl -o /dev/null -s -w "dns=%{time_namelookup} connect=%{time_connect} ttfb=%{time_starttransfer} total=%{time_total}\n" https://api.example.com/status
Se já o dns se destacar, a culpa é da resolução de nomes e não da outra ponta, com a contraprova a fazer-se com time getent hosts api.example.com. No código, cada chamada externa precisa de um prazo próprio e curto: com o cURL são o CURLOPT_CONNECTTIMEOUT e o CURLOPT_TIMEOUT. Quem em vez disso aplicar file_get_contents() a um endereço vai parar ao default_socket_timeout do php.ini, onde por predefinição estão 60 segundos:
grep -n "default_socket_timeout" /etc/php/8.4/fpm/php.ini
A regra prática: a soma de todos os prazos externos de um pedido tem de ficar abaixo do fastcgi_read_timeout. Caso contrário, o nginx corta antes de o seu código conseguir apresentar uma página de erro compreensível.
Sistema de ficheiros e memória RAM
Um disco cheio torna as escritas lentas ou impossíveis, e isso atinge ao mesmo tempo os ficheiros de sessão, as caches e os logs. Verifique as duas coisas, o espaço em disco e os inodes:
df -h
df -i
O segundo comando é o que costuma ser esquecido. Uma partição pode estar a 40% de ocupação e mesmo assim não aceitar mais nenhum ficheiro, se os inodes estiverem esgotados. O segundo candidato é a falta de memória: se o sistema começar a fazer swap, todos os pedidos ficam pesados, sem que haja uma consulta concreta com culpa nisso.
vmstat 1 5
Se as colunas si e so se mantiverem permanentemente acima de zero, o kernel está a mover páginas para dentro e para fora sem parar, e o problema é de memória e não de tempo. A forma correta de lidar com isso está em configurar swap e evitar o Out-of-Memory. O terceiro candidato são os sistemas de ficheiros em rede: um ponto de montagem NFS encravado bloqueia qualquer processo que lhe toque, e isso inclui o seu comando de diagnóstico. Coloque, por isso, um prazo à frente:
timeout 5 df -h
As tarefas longas não pertencem ao pedido
Há tarefas que demoram, e não há volta a dar: um relatório anual, uma importação com 200 000 linhas, uma conversão de imagens. O erro não está em demorarem muito, está em haver um servidor web à espera. A forma correta tem três partes:
- O pedido cria uma tarefa, numa tabela ou numa fila de espera, e responde de imediato. O código de estado adequado é o 202, acompanhado de um endereço onde se pode consultar o ponto de situação.
- Um processo de trabalho fora do nginx vai buscar as tarefas e executa-as. Não tem nenhum prazo a apertá-lo, porque ninguém está à espera dele.
- A interface consulta o ponto de situação. Essa consulta é sempre rápida, por mais tempo que a tarefa demore.
Ponha esse processo de trabalho a correr como serviço próprio, para que volte sozinho depois de um crash e depois de um reinício. Como é uma unit dessas mostra-o criar um serviço systemd. Para tarefas em intervalos fixos chega um cronjob, e aí precisa de um bloqueio para que duas execuções não se ultrapassem. Com -n, a segunda execução termina de imediato em vez de ficar à espera:
flock -n /run/lock/kh-worker.lock /usr/bin/php /var/www/html/worker.php
Nas frameworks correntes a fila de espera já vem pronta, só precisa de ser posta a correr: no Laravel com php artisan queue:work, no Symfony com php bin/console messenger:consume seguido do nome do seu transporte. Ambos pertencem a uma unit do systemd, não a uma janela de terminal.
Há um caso especial que merece menção expressa: por predefinição, o WordPress arranca as tarefas agendadas dentro dos pedidos dos visitantes. Um visitante paga, portanto, com o seu tempo de espera para que corra em segundo plano uma verificação de atualizações. A linha define('DISABLE_WP_CRON', true); no wp-config.php, acima da referência a wp-settings.php, desliga esse comportamento. A seguir, chame regularmente as tarefas devidas por si próprio:
wp cron event run --due-now --path=/var/www/html
O que tiver mesmo de continuar síncrono recebe um bloco location próprio com prazo próprio e, além disso, uma limitação, para que esse caminho não ocupe sozinho todos os processos de trabalho: limit_conn_zone $binary_remote_addr zone=export:10m; no bloco http e limit_conn export 1; no bloco em causa. As tentativas seguintes recebem então um 503 em vez de um servidor ocupado.
Erros frequentes e soluções
| Mensagem na formulação literal | Significado e solução |
|---|---|
upstream timed out (110: Connection timed out) while reading response header from upstream | O caso normal. O backend demora demasiado a processar. Avalie o log de tempos e determine o que consome o tempo antes de mexer no prazo. |
upstream timed out (110: Connection timed out) while connecting to upstream | O estabelecimento da ligação esgotou o prazo. Num backend remoto é quase sempre um filtro de pacotes que descarta em vez de rejeitar, num PHP-FPM local é uma fila de espera cheia no socket. |
upstream timed out (110: Connection timed out) while reading upstream | Os cabeçalhos chegaram e depois o corpo travou por mais tempo do que o prazo. Típico de exportações que a meio ficam muito tempo a processar. |
nginx: [emerg] "fastcgi_read_timeout" directive is not allowed here in /etc/nginx/nginx.conf:12 | A diretiva está fora de http, server ou location, normalmente por descuido logo no topo do ficheiro. |
nginx: [emerg] unknown directive "proxy_read_timout" in /etc/nginx/sites-enabled/example.com:31 | Erro de escrita. O nginx verifica nomes, não intenções. O número da linha está na mensagem. |
PHP Fatal error: Maximum execution time of 30 seconds exceeded in /var/www/html/export.php on line 42 | Aqui o limite de tempo de execução do PHP atuou, excecionalmente, ou seja, o script esteve a processar e não à espera. Dá um 500 ou uma página em branco, não um 504. |
SQLSTATE[HY000]: General error: 1205 Lock wait timeout exceeded; try restarting transaction | Outra transação está a segurar a linha, e o prazo está por predefinição em 50 segundos. A causa é quase sempre uma transação que ficou aberta demasiado tempo. |
cURL error 28: Operation timed out after 60000 milliseconds | Uma interface externa não responde. Defina no código um prazo próprio e mais curto, para que a sua aplicação mantenha o controlo. |
504 no browser, mas a procura por upstream timed out não devolve nada | O 504 não vem deste nginx, vem de um serviço a montante, como um load balancer ou um segundo proxy. Alguns registam para isso um código de estado próprio. |
| A interrupção continua a acontecer ao fim de exatamente 60 segundos, apesar de o prazo estar em 300 | A diretiva alterada pertence ao módulo errado, ou ganha outro bloco. O nginx -T mostra o que vale de facto. |
Como reconhecer que o problema ficou resolvido
Um comando sem mensagem de erro não prova nada, e uma única chamada bem-sucedida também não. Quatro provas que, em conjunto, sustentam a conclusão:
- Código de estado e duração diretamente no servidor, para que nenhuma cache embeleze o resultado. Espera-se um 200 e uma duração claramente abaixo do prazo. Um 200 ao fim de 58 segundos com um prazo de 60 não é um êxito, é a próxima avaria assim que a carga subir um pouco. E o que conta é a pior de vinte chamadas:
for i in $(seq 1 20); do curl -s -o /dev/null -w "%{http_code} %{time_total}\n" -H "Host: example.com" http://127.0.0.1/report.php; done | sort -k2 -n | tail -3 - O log de erros mantém-se em silêncio. Antes do teste, esvazie-o com
truncate -s 0 /var/log/nginx/error.log, desencadeie as chamadas e volte a olhar. Um ficheiro vazio é a verdadeira prova. - A fila de espera do PHP-FPM está a zero. Enquanto houver algo à espera em
listen queuena página de estado, a causa apenas mudou de sítio. - Um reinício não muda nada. É o ponto mais vezes saltado. Valores vindos de
SET GLOBAL, processos lançados à mão e diretórios criados manualmente debaixo de/runnão lhe sobrevivem. Verifique comsystemctl is-enabled nginx php8.4-fpme reinicie o servidor uma vez de forma controlada, enquanto ainda está a acompanhar.
Esse reinício, com acesso à consola incluído, faz-se na área de cliente nos servidores root KVM e nos servidores dedicados da KernelHost, mesmo quando o serviço web já não está a entregar nada. Os servidores estão no datacenter maincubes em Frankfurt am Main (certificado TÜV TIER3+), com filtragem a montante na rede que está à frente.
Lista de verificação rápida para uma emergência
- Contar os códigos de estado no log de acessos. Os 504, 499 e 502 lado a lado dizem mais do que qualquer um deles isolado.
grep "upstream timed out" /var/log/nginx/error.log, anotar a fase indicada na mensagem.- Ativar o log de tempos e comparar
uct,uhteurt. Se a duração bater ao segundo com o prazo configurado, foi o prazo que atuou. - Determinar o que consome o tempo: base de dados, interface externa, sistema de ficheiros ou memória.
- Com o
nginx -T, verificar que prazo vale neste bloco antes de alterar algum. - Só depois decidir: corrigir, passar para uma fila de espera ou, em último recurso, aumentar o prazo apenas para esse caminho concreto.
- Depois da correção: esvaziar o log, medir vinte chamadas, reiniciar o servidor uma vez de forma controlada.
Perguntas frequentes
Qual é a diferença entre o 502 Bad Gateway e o 504 Gateway Time-out?
Defini o fastcgi_read_timeout em 300 segundos e a página continua a falhar ao fim de 60 segundos. A que se deve?
Um prazo mais alto resolve o problema?
Que linha do log de erros corresponde a um 504?
Porque é que o max_execution_time não termina o meu script PHP encravado?
Como sei se foi o prazo que atuou ou se o backend desistiu por si próprio?
O browser mostra 504, mas a procura por "upstream timed out" não devolve nada. De onde vem o erro?
O que faço com tarefas que, por natureza, demoram mais do que qualquer prazo razoável?
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.

