Risolvere l'errore nginx 504 Gateway Time-out: trovare la causa invece di alzare il termine
Con un 504 il backend era raggiungibile, ha solo risposto troppo lentamente. Come stabilire con il log dei tempi dove finisce il tempo, quale dei molti termini vale davvero e perché un termine più alto di solito sposta soltanto il disservizio.
Un 504 Gateway Time-out è la più paziente di tutte le pagine di errore. nginx ha accettato la richiesta, l'ha inoltrata al backend e poi ha aspettato fino allo scadere di un termine impostato internamente. Il backend è rimasto raggiungibile per tutto il tempo, semplicemente non ha risposto entro la scadenza. È esattamente qui che sta la differenza con il codice vicino: con il 502 Bad Gateway il backend risponde male oppure non risponde affatto, con il 504 risponde troppo lentamente. Questa guida ti mostra come misurare dove il tempo viene davvero consumato, quale dei molti termini entri realmente in gioco e perché alzare quel limite è quasi sempre la peggiore delle risposte a disposizione.
Tutte le indicazioni valgono per Debian 13 (trixie), Debian 12 (bookworm), Ubuntu 24.04 LTS e Ubuntu 22.04 LTS. I comandi sono scritti per l'esecuzione come root, se lavori come utente normale anteponi sudo. Negli esempi compare PHP 8.4, sostituisci il numero di versione con quello del tuo sistema:
| Sistema | PHP | Servizio | Configurazione |
|---|---|---|---|
| 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/
Le direttive di nginx portano lo stesso nome su tutti e quattro i sistemi. Le differenze stanno sul lato PHP e sul lato database, e sono segnalate nei punti in cui contano.
Chi ha mollato per primo decide da che parte cercare
Prima di aprire un file, rispondi a una domanda: quale livello ha interrotto? Il codice di stato lo dice già.
| Codice | Che cosa è successo | Dove cercare |
|---|---|---|
| 500 Internal Server Error | Il backend ha risposto, la risposta era un errore | Log dell'applicazione |
| 502 Bad Gateway | La connessione non si è stabilita oppure è caduta | Servizio, socket, permessi, crash |
| 504 Gateway Time-out | La connessione reggeva, la risposta non è arrivata entro il termine | Tempo di esecuzione nel backend |
| 408 Request Timeout | Il visitatore non ha finito di inviare in tempo la propria richiesta | Upload, connessioni lente |
| 499 (solo nel log) | Il visitatore ha interrotto prima che nginx finisse | Troppo lento, ma sotto il termine |
La riga con 499 è la più sottovalutata di tutte. Non è un errore, è un campanello d'allarme: il visitatore ha chiuso la scheda perché la pagina ci metteva troppo. Se un 504 sparisce dopo che hai alzato il termine e al suo posto compaiono dei 499, non hai risolto niente.
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head
Il campo 9 vale per il formato standard combined. Molti 504 a fronte di pochi 499 indicano singole pagine pesanti, il quadro opposto indica un'applicazione lenta in modo uniforme.
Prima di cambiare qualcosa: la via del ritorno
La diagnosi delle prossime due sezioni è di sola lettura. Il rischio comincia dove metti mano alle configurazioni, e lì il servizio può fermarsi in tre modi: una configurazione di nginx difettosa impedisce l'avvio del server web, un file del pool difettoso impedisce l'avvio di PHP-FPM, e limiti alzati con troppa generosità possono esaurire la RAM. L'ultimo caso è il più sgradevole, perché a quel punto il kernel termina dei processi e non colpisce per forza il responsabile. Se ci va di mezzo il servizio SSH, il server non è più governabile dalla rete.
Crea quindi prima di tutto delle copie, sotto /root e mai nella directory web. Il timestamp nel nome è importante, perché raramente si interviene una volta sola e cp -a sovrascrive senza dire niente una copia già presente:
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)
La via del ritorno sono tre righe, e l'ordine non è casuale:
cp -a /root/backups/example.com.2026-09-03-1030 /etc/nginx/sites-available/example.com
nginx -t
systemctl reload nginx
Finché puoi, usa reload al posto di restart. reload applica la nuova configurazione soltanto se è priva di errori. Un restart invece termina per primo il processo in esecuzione e in caso di errore ti lascia senza server web.
Se il server non risponde più affatto, sui server root KVM e sui server dedicati di KernelHost apri la console VNC dall'area clienti. È agganciata al livello di virtualizzazione, o al collegamento stesso, e non allo stack di rete del sistema ospite: funziona quindi anche quando nessun servizio è più raggiungibile. Accedi una volta in anticipo e assicurati di conoscere la password di root. Comando di verifica dopo ogni intervento:
systemctl is-active nginx php8.4-fpm
free -m
La riga nel log degli errori che decide il caso
Un 504 lascia sempre una traccia:
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:"
A contare non è il numero di errore 110, identico in ogni 504, ma la fase che lo segue:
while connecting to upstream: la connessione non si è stabilita. Con un backend remoto è quasi sempre un filtro dei pacchetti che scarta i pacchetti invece di rifiutarli, perché un rifiuto tornerebbe indietro subito e produrrebbe un 502.while sending request to upstream: nginx non è riuscito a consegnare il corpo della richiesta, tipico con gli upload di grandi dimensioni.while reading response header from upstream: il caso normale. Il backend ha ricevuto tutto ed è al lavoro, senza spedire nemmeno la prima riga di intestazione.while reading upstream: le intestazioni sono arrivate, poi il corpo si è inceppato. Lo vedi con le risposte in streaming e con le esportazioni.
grep -n "upstream timed out" /var/log/nginx/error.log | tail -20
Molti host virtuali scrivono in un log degli errori proprio. Dove si trova quel file e perché cercando in sites-enabled serve la -R maiuscola è spiegato nell'articolo sul 502. Se la ricerca resta vuota mentre il browser mostra un 504, l'errore non arriva da questo nginx.
Dove finisce il tempo: misurare invece di indovinare
nginx può registrare per ogni richiesta quanto tempo ha impiegato il backend. È il passo più importante della diagnosi, perché risponde senza congetture alla domanda "applicazione o connessione". Nel blocco http di /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"';
Nel blocco server interessato aggiungi una seconda riga di log. Il log degli accessi esistente resta intatto, nginx scrive in entrambi:
access_log /var/log/nginx/timing.log kh_timing;
nginx -t
systemctl reload nginx
tail -n 5 /var/log/nginx/timing.log
Dopo qualche minuto di esercizio porta in cima le richieste più lente:
awk '{ t=$3; sub(/^rt=/, "", t); print t, $0 }' /var/log/nginx/timing.log | sort -rn | head -20
| Osservazione | Interpretazione | Passo successivo |
|---|---|---|
| uct alto con backend locale | L'apertura della connessione si inceppa | Coda del socket piena, risoluzione dei nomi lenta |
| uht e urt quasi uguali, entrambi alti | Il backend elabora prima di inviare la prima intestazione | Applicazione, database, interfaccia esterna |
| uht basso, urt alto | L'intestazione è arrivata subito, il corpo arriva a gocce | Streaming, esportazioni, cicli su molti record |
| urt basso, rt alto | Il backend è stato veloce, il tempo si è perso dopo | Connessione del visitatore, risposta molto grande |
| Un trattino al posto di un numero | Non è stato coinvolto nessun backend | File statico oppure interruzione prima dell'inoltro |
Due dettagli fanno risparmiare parecchio tempo. Più valori separati da virgola in un campo significano che la richiesta è andata a più di una destinazione, quindi c'è stato un secondo tentativo. E l'indizio più forte in assoluto: se la durata misurata corrisponde al secondo al valore configurato, per esempio 60,001 secondi con un termine di 60, allora è scattato il termine e non è stato il backend a mollare da solo. Valori irregolari come 43,7 secondi mostrano che a frenare è stato qualcos'altro.
Quale termine entra davvero in gioco
Per i timeout nginx conosce una buona dozzina di direttive, e l'ora sprecata più di frequente nasce dal fatto che qualcuno mette mano a quella sbagliata. Quale sia valida dipende da quale modulo elabora il blocco location.
| Direttiva | Predefinito | Vale nei blocchi con | Effetto allo scadere |
|---|---|---|---|
| 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, il termine decisivo con i backend proxy |
| fastcgi_connect_timeout | 60s | fastcgi_pass | 504, come sopra, per PHP-FPM |
| fastcgi_send_timeout | 60s | fastcgi_pass | 504, come sopra |
| fastcgi_read_timeout | 60s | fastcgi_pass | 504, il termine decisivo con PHP |
| send_timeout | 60s | ovunque | nessun 504, viene chiusa la connessione verso il visitatore |
| client_body_timeout | 60s | ovunque | 408, non 504 |
Il termine vale tra due operazioni di lettura, non per l'intera risposta. Un download che dura dieci minuti e nel frattempo consegna dati senza interruzione arriva in fondo. Un backend che tace per 61 secondi viene buttato fuori. Con le esportazioni che si inceppano aiuta quindi più spesso far produrre qualcosa a intervalli regolari all'applicazione, invece di alzare il termine.
send_timeout non serve a niente contro un 504. Questo termine riguarda la trasmissione verso il visitatore. Se scade non ottieni una pagina di errore, ma un download interrotto. Diventa rilevante solo quando disattivi il buffering con proxy_buffering off;, perché a quel punto un visitatore lento frena fino dentro al backend.
nginx accetta anche direttive che nel blocco in questione non hanno alcun effetto. Un proxy_read_timeout 300s; dentro un blocco PHP con fastcgi_pass è sintatticamente ineccepibile, nginx -t risponde syntax is ok, e la pagina continua a interrompersi dopo 60 secondi. Al contrario vale lo stesso. È la causa più frequente per cui un termine alzato resta senza effetto. Che cosa valga davvero lo mostra la configurazione assemblata:
nginx -T | grep -E "read_timeout|send_timeout|fastcgi_pass|proxy_pass"
Se un singolo percorso deve davvero girare più a lungo, imposta il termine esattamente lì e da nessun'altra parte. Il segno di uguale ne fa una corrispondenza esatta, che vince sul blocco generico location ~ \.php$ incaricato degli altri file 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: perché qui max_execution_time interviene di rado
L'ipotesi più ovvia è che PHP chiuda da solo uno script bloccato. Proprio in questo caso quasi mai è così. Prima la trappola nella misurazione: php -i interroga la variante da riga di comando, che usa una configurazione propria sotto /etc/php/8.4/cli/ e gira comunque senza limite di esecuzione, quindi non dice niente su FPM. A contare sono questi due punti:
grep -n "^max_execution_time" /etc/php/8.4/fpm/php.ini
grep -rn "max_execution_time" /etc/php/8.4/fpm/pool.d/
Nel file del pool il valore può essere sovrascritto con php_value[max_execution_time] oppure php_admin_value[max_execution_time]. Un valore impostato con php_admin_value non è più modificabile dall'applicazione tramite ini_set(). Se il tuo framework alza da solo il tempo di esecuzione e all'improvviso questo non ha più effetto, la causa è proprio questa.
E ora il punto vero: su Linux il tempo di attesa nelle chiamate di sistema non viene conteggiato. L'orologio gira solo finché lo script sta effettivamente elaborando. Se aspetta una query al database, un'interfaccia esterna o il filesystem, resta fermo. Uno script può quindi restare incollato dieci minuti a una query bloccata senza che il limite di esecuzione scatti mai. A chiuderlo è allora il termine di nginx, e il risultato è il 504.
Ne segue una regola scomoda: max_execution_time protegge dai cicli infiniti nel tuo codice, non dall'attesa. L'unico limite duro sul lato PHP è request_terminate_timeout nel file del pool, che rimuove il processo di lavoro a prescindere da che cosa lo tenga bloccato. Produce però un 502 e non un 504. Ordina i termini in modo crescente dall'interno verso l'esterno, così interviene per primo il livello che può ancora produrre un messaggio comprensibile. Con gli script che elaborano funziona, con quelli che aspettano no, per il motivo appena detto. Lì resta solo una strada, limitare l'attesa stessa.
Perché alzare il termine è quasi sempre la risposta sbagliata
Fai due conti. Un pool con pm.max_children = 10 ha dieci processi di lavoro. Una pagina impiega 90 secondi. Dieci chiamate contemporanee occupano quindi ognuno di quei processi per un minuto e mezzo. In quel lasso di tempo nessuno riceve più una pagina PHP, nemmeno la pagina iniziale. Da una sottopagina lenta è nato un disservizio completo. Tre meccanismi lo amplificano:
- Il visitatore ricarica. Questo genera una richiesta in più, ma non libera quella vecchia. PHP si accorge di un visitatore che ha interrotto solo quando lo script produce di nuovo qualcosa in output, e uno script che elabora non produce niente per parecchio tempo.
- nginx ritenta da solo. In un blocco
upstreamcon più destinazioni,proxy_next_upstreamè impostato per impostazione predefinita suerror timeout. Una richiesta scaduta passa al server successivo e la query costosa gira una seconda volta. Le richieste in scrittura sono escluse, quelle in lettura no. Si disattiva conproxy_next_upstream error;. - Anche il monitoraggio ritenta. Un intervallo di controllo di 60 secondi su una pagina che ne richiede 90 genera un carico permanente che non viene mai smaltito.
A questo si aggiunge che nessuno aspetta cinque minuti davanti a una pagina web. Un termine di 300 secondi trasforma un problema da un minuto in un problema da cinque minuti, e il visitatore se n'è andato da un pezzo mentre il processo di lavoro continua a elaborare.
Quanto sia davvero pieno lo mostra la pagina di stato di PHP-FPM. Imposta pm.status_path = /fpm-status nel file del pool e crea nel blocco server un accesso raggiungibile solo in locale:
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
Guarda active processes, listen queue e max active processes. Se listen queue resta stabilmente sopra lo zero, il numero di processi di lavoro non basta per l'attuale tempo di esecuzione delle pagine. È il momento in cui devi abbassare quel tempo, non alzare il termine.
I tre soliti divoratori di tempo
Database
Nella maggior parte dei casi il tempo si nasconde qui. Guarda prima di tutto che cosa sta girando in questo momento. Su tutti e quattro i sistemi l'accesso root passa per impostazione predefinita dal socket Unix, quindi il comando funziona senza password:
mysql -e "SHOW FULL PROCESSLIST;"
Interessano le colonne Time e State. Valori come Sending data o Waiting for table metadata lock con secondi a due cifre sono il tuo caso. Per cercare in modo sistematico usa il log delle query lente, attivabile a servizio acceso:
mysql -e "SET GLOBAL slow_query_log = 1; SET GLOBAL long_query_time = 1;"
mysql -e "SHOW VARIABLES LIKE 'slow_query_log_file';"
Il nome del file lo leggi dal secondo output, perché cambia da sistema a sistema: Debian punta di solito su MariaDB e scrive in /var/log/mysql/mariadb-slow.log, su Ubuntu il file ha un altro nome a seconda del server installato. Dopo qualche minuto analizza e disattiva di nuovo, perché il log costa carico in scrittura:
mysqldumpslow -s t /var/log/mysql/mariadb-slow.log | head -30
mysql -e "SET GLOBAL slow_query_log = 0;"
L'opzione -s t ordina per tempo complessivo. La query più costosa la esamini con EXPLAIN, quasi sempre manca un indice proprio sulla colonna usata per filtrare o ordinare. SET GLOBAL ha effetto immediato, ma non sopravvive a un riavvio del database. Un limite massimo per singola query esiste anche sul lato database: MariaDB conosce max_statement_time in secondi, MySQL max_execution_time in millisecondi, quest'ultimo solo per le query in lettura. Così la tua applicazione riceve un errore pulito invece di tenere occupato un processo di lavoro.
Interfacce esterne
Se la tua pagina interroga a ogni chiamata un fornitore di servizi di pagamento o un server di licenze, il guasto di quel servizio diventa il tuo 504. Misura la chiamata separatamente, dal server:
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 è già dns a saltare all'occhio, la colpa è della risoluzione dei nomi e non della controparte, controprova con time getent hosts api.example.com. Nel codice ogni chiamata esterna ha bisogno di un termine proprio e stretto: con cURL sono CURLOPT_CONNECTTIMEOUT e CURLOPT_TIMEOUT. Chi invece usa file_get_contents() su un indirizzo finisce su default_socket_timeout del php.ini, dove per impostazione predefinita ci sono 60 secondi:
grep -n "default_socket_timeout" /etc/php/8.4/fpm/php.ini
La regola pratica: la somma di tutti i termini esterni di una richiesta deve restare sotto fastcgi_read_timeout. Altrimenti nginx taglia prima che il tuo codice possa produrre una pagina di errore comprensibile.
Filesystem e RAM
Un disco pieno rende le scritture lente o impossibili, e questo colpisce insieme file di sessione, cache e log. Verifica entrambe le cose, lo spazio e gli inode:
df -h
df -i
Il secondo comando è quello che si dimentica più spesso. Una partizione può essere occupata al 40% e non accogliere comunque nessun nuovo file, se gli inode sono esauriti. Il secondo candidato è la carenza di memoria: se il sistema comincia a usare lo swap, ogni richiesta diventa lenta senza che la colpa sia di una query precisa.
vmstat 1 5
Se le colonne si e so restano stabilmente sopra lo zero, il kernel scarica e ricarica memoria senza sosta e il tuo è un problema di memoria, non di tempo. Come affrontarlo in modo pulito è spiegato in configurare lo swap ed evitare l'Out-of-Memory. Il terzo candidato sono le unità di rete: un mount NFS bloccato blocca ogni processo che lo tocca, e questo vale anche per il tuo comando di diagnosi. Mettici quindi davanti un termine:
timeout 5 df -h
I compiti lunghi non stanno dentro la richiesta
Certi compiti richiedono tempo, punto: un rapporto annuale, un'importazione da 200.000 righe, una conversione di immagini. L'errore non sta nel fatto che durino a lungo, ma nel fatto che un server web li stia ad aspettare. La forma pulita ha tre parti:
- La richiesta crea un job, in una tabella o in una coda, e risponde subito. Il codice di stato adatto è 202, insieme a un indirizzo dal quale si può interrogare lo stato di avanzamento.
- Un processo di lavoro fuori da nginx preleva i job e li porta a termine. Non ha nessun termine sul collo, perché nessuno lo sta aspettando.
- L'interfaccia interroga lo stato di avanzamento. Quella interrogazione è sempre veloce, per quanto lungo sia il compito.
Il processo di lavoro fallo girare come servizio a sé, così torna da solo dopo un crash e dopo un riavvio. Come è fatta una unit del genere lo mostra creare un servizio systemd. Per i compiti a intervalli fissi basta un cronjob, e lì ti serve un lock perché due esecuzioni non si sovrappongano. Con -n la seconda esecuzione si interrompe subito invece di mettersi in attesa:
flock -n /run/lock/kh-worker.lock /usr/bin/php /var/www/html/worker.php
Per i framework più diffusi la coda esiste già pronta, va soltanto messa in esercizio: con Laravel php artisan queue:work, con Symfony php bin/console messenger:consume seguito dal nome del tuo transport. Entrambi vanno in una unit systemd, non in una finestra di terminale.
Un caso particolare merita una menzione esplicita: per impostazione predefinita WordPress avvia le attività pianificate dentro le richieste dei visitatori. Un visitatore paga quindi con la propria attesa il fatto che in background giri un controllo degli aggiornamenti. La riga define('DISABLE_WP_CRON', true); in wp-config.php, sopra il riferimento a wp-settings.php, disattiva questo comportamento. Da lì in poi le attività in scadenza le richiami tu stesso a intervalli regolari:
wp cron event run --due-now --path=/var/www/html
Quello che deve per forza restare sincrono riceve un blocco location proprio, con un termine proprio e in più un limite, così quell'unico percorso non occupa tutti i processi di lavoro: limit_conn_zone $binary_remote_addr zone=export:10m; nel blocco http e limit_conn export 1; nel blocco interessato. Gli ulteriori tentativi ricevono allora un 503 invece di trovare un server occupato.
Errori frequenti e soluzioni
| Messaggio parola per parola | Significato e rimedio |
|---|---|
upstream timed out (110: Connection timed out) while reading response header from upstream | Il caso normale. Il backend impiega troppo tempo. Analizza il log dei tempi e individua il divoratore di tempo prima di mettere mano al termine. |
upstream timed out (110: Connection timed out) while connecting to upstream | L'apertura della connessione è finita nel termine. Con un backend remoto è quasi sempre un filtro dei pacchetti che scarta invece di rifiutare, con PHP-FPM locale una coda del socket piena. |
upstream timed out (110: Connection timed out) while reading upstream | Le intestazioni sono arrivate, poi il corpo si è fermato più a lungo del termine. Tipico delle esportazioni che a metà strada elaborano a lungo. |
nginx: [emerg] "fastcgi_read_timeout" directive is not allowed here in /etc/nginx/nginx.conf:12 | La direttiva si trova fuori da http, server o location, di solito per sbaglio in cima al file. |
nginx: [emerg] unknown directive "proxy_read_timout" in /etc/nginx/sites-enabled/example.com:31 | Errore di battitura. nginx controlla i nomi, non le intenzioni. Il numero di riga è scritto nel messaggio. |
PHP Fatal error: Maximum execution time of 30 seconds exceeded in /var/www/html/export.php on line 42 | Qui il limite di esecuzione di PHP è intervenuto per una volta, quindi lo script stava elaborando e non aspettando. Produce un 500 o una pagina vuota, non un 504. |
SQLSTATE[HY000]: General error: 1205 Lock wait timeout exceeded; try restarting transaction | Un'altra transazione tiene la riga, il termine predefinito è di 50 secondi. La causa è quasi sempre una transazione rimasta aperta troppo a lungo. |
cURL error 28: Operation timed out after 60000 milliseconds | Un'interfaccia esterna non risponde. Imposta nel codice un termine tuo, più breve, così la tua applicazione mantiene il controllo. |
504 nel browser, ma la ricerca di upstream timed out resta vuota | Il 504 non arriva da questo nginx, ma da un servizio a monte come un load balancer o un secondo proxy. Alcuni segnalano per questo un codice di stato proprio. |
| L'interruzione avviene ancora esattamente dopo 60 secondi, anche se il termine è impostato su 300 | La direttiva modificata appartiene al modulo sbagliato, oppure vince un altro blocco. nginx -T mostra che cosa vale davvero. |
Come riconosci che il problema è risolto
Un comando senza messaggio di errore non dimostra niente, e nemmeno una singola chiamata andata a buon fine. Quattro prove che insieme reggono:
- Codice di stato e durata direttamente sul server, così nessuna cache abbellisce il risultato. Ci si aspetta un 200 e una durata ben al di sotto del termine. Un 200 dopo 58 secondi con un termine di 60 non è un successo, è il prossimo disservizio appena il carico sale un poco. Quello che conta è la peggiore di venti chiamate:
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 - Il log degli errori resta muto. Prima del test azzeralo con
truncate -s 0 /var/log/nginx/error.log, scatena le chiamate e guardaci di nuovo dentro. Un file vuoto è la prova vera. - La coda di PHP-FPM è a zero. Finché nella pagina di stato sotto
listen queuec'è qualcosa in attesa, la causa è soltanto spostata. - Un riavvio non cambia niente. È il punto che si salta più spesso. I valori impostati con
SET GLOBAL, i processi avviati a mano e le directory create da te sotto/runnon gli sopravvivono. Verificasystemctl is-enabled nginx php8.4-fpme riavvia il server una volta in modo controllato, finché sei ancora lì a guardare.
Questo riavvio, accesso alla console compreso, sui server root KVM e sui server dedicati di KernelHost lo esegui dall'area clienti, anche quando il servizio web in quel momento non consegna più niente. I server si trovano nel datacenter maincubes di Francoforte sul Meno (certificato TÜV TIER3+), con un filtraggio a monte nella rete che li precede.
Checklist rapida per l'emergenza
- Conta i codici di stato nel log degli accessi. 504, 499 e 502 messi uno accanto all'altro dicono più di ognuno di loro preso da solo.
grep "upstream timed out" /var/log/nginx/error.log, annota la fase indicata nel messaggio.- Attiva il log dei tempi e confronta
uct,uhteurt. Se la durata coincide al secondo con il termine configurato, è scattato il termine. - Individua il divoratore di tempo: database, interfaccia esterna, filesystem o memoria.
- Con
nginx -Tverifica quale termine vale in quel blocco, prima di modificarne uno. - Solo dopo decidi: risolvere, spostare in una coda oppure, come ultima scelta, alzare il termine per quell'unico percorso.
- Dopo il fix: azzera il log, misura venti chiamate, riavvia il server una volta in modo controllato.
Domande frequenti
Qual è la differenza tra 502 Bad Gateway e 504 Gateway Time-out?
Ho impostato fastcgi_read_timeout su 300 secondi, ma la pagina si interrompe comunque dopo 60 secondi. Da che cosa dipende?
Un termine più alto risolve il problema?
Quale riga del log degli errori appartiene a un 504?
Perché max_execution_time non chiude il mio script PHP bloccato?
Come riconosco se è scattato il termine oppure se è stato il backend a mollare da solo?
Il browser mostra 504, ma la ricerca di "upstream timed out" resta vuota. Da dove arriva l'errore?
Che cosa faccio con i compiti che per natura durano più a lungo di qualsiasi termine ragionevole?
2026 KernelHost GmbH. Tutti i diritti riservati. Questa guida è protetta dal diritto d'autore. La ripubblicazione su altri siti web, anche parziale o in forma modificata, non è consentita senza il nostro consenso scritto. Le citazioni con indicazione della fonte e un link sono le benvenute.

