L’errore 504 Gateway Timeout è uno degli errori HTTP più comuni in ambienti che utilizzano nginx come reverse proxy. A differenza dell’errore 502 Bad Gateway, che indica una risposta non valida o assente dal backend, l’errore 504 ha una causa più specifica: il backend ha impiegato troppo tempo a rispondere e nginx ha abbandonato l’attesa prima di ricevere una risposta.
Nginx, agendo come gateway o proxy, non riceve una risposta dall’upstream server entro il tempo configurato per l’attesa. A quel punto, per non tenere occupate le risorse indefinitamente, interrompe l’attesa e restituisce un errore 504 al client.
Possiamo schematizzare il flusso in questo modo

Come per l’errore 502, il punto di partenza per individuare il problema è sempre la lettura dei log di nginx
grep " 504 " /var/log/nginx/access.log | tail -20Questo comando mostra quali URL generano l’errore 504 e con quale frequenza. Se l’errore compare solo su determinate pagine (ad esempio quelle che eseguono import, report o elaborazioni pesanti), la causa è quasi certamente uno script lento.
Per identificare le richieste più lente, è possibile abilitare il logging dei tempi di risposta dell’upstream modificando il file di configurazione (/etc/nginx/nginx.conf)
http {
log_format upstream_time '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'rt=$request_time uct="$upstream_connect_time" '
'uht="$upstream_header_time" urt="$upstream_response_time"';
access_log /var/log/nginx/access.log upstream_time;
}Dopo il reload di nginx, ogni riga del log includerà il tempo di risposta dell’upstream (urt). Ora, per estrarre le richieste più lente possiamo usare questo comando
awk '{print $NF, $7}' /var/log/nginx/access.log | sort -rn | head -20Come visto in precedenza per l’errore 502, nel file error.log di nginx possiamo trovare informazioni sulla causa
grep -E "timed out|upstream" /var/log/nginx/error.log | tail -20
# Esempio di riga di log
upstream timed out (110: Connection timed out) while reading response header from upstream,
client: 192.168.1.100, server: dominio.it,
request: "GET /report.php HTTP/1.1",
upstream: "fastcgi://unix:/run/php/php8.2-fpm.sock",
host: "dominio.it"La parte “while reading response header from upstream” indica che nginx ha stabilito la connessione con PHP-FPM, ma quest’ultimo non ha inviato la risposta entro il timeout configurato.
I timeout di Nginx
Per il modulo ngx_http_proxy_module (proxy verso backend HTTP) esistono tre direttive di timeout. Nel 99% dei casi di errore 504, proxy_read_timeout è il responsabile: il backend è lento a rispondere, non lento ad accettare la connessione.
Per le applicazioni PHP con FastCGI, le direttive equivalenti sono
| Direttiva | Descrizione | Default |
|---|---|---|
| fastcgi_connect_timeout | Tempo massimo per stabilire la connessione con PHP-FPM | 60s |
| fastcgi_send_timeout | Tempo massimo per inviare la richiesta a PHP-FPM | 60s |
| fastcgi_read_timeout | Tempo massimo per ricevere la risposta da PHP-FPM | 60s |
Per i backend HTTP (Node.js, altro Nginx, Apache)
| Direttiva | Descrizione | Default |
|---|---|---|
| proxy_connect_timeout | Tempo per stabilire la connessione | 60s |
| proxy_send_timeout | Tempo per inviare la richiesta | 60s |
| proxy_read_timeout | Tempo per ricevere la risposta | 60s |
Timeout FastCGI troppo breve
È la causa più frequente. Lo script PHP esegue un’operazione che supera il timeout predefinito di 60 secondi. Ad esempio un import di dati, un report su larga scala, una chiamata a un’API esterna e nginx abbandona l’attesa prima che PHP-FPM abbia terminato.
# Messaggio nel log
upstream timed out (110: Connection timed out) while reading response header from upstream
# Aumentare il timeout in Nginx - /etc/nginx/sites-enabled/il-tuo-sito
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_read_timeout 120;
fastcgi_send_timeout 120;
fastcgi_connect_timeout 120;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
# Allineare il timeout in PHP-FPM - /etc/php/8.2/fpm/pool.d/www.conf
request_terminate_timeout = 120Regola fondamentale: ogni layer esterno deve avere un timeout maggiore o uguale al layer interno. Il timeout di nginx (fastcgi_read_timeout) deve essere maggiore o uguale al valore request_terminate_timeout di PHP-FPM. Se PHP-FPM termina lo script dopo 120 secondi ma nginx abbandona dopo 60, si otterrà sempre un errore 504 perché PHP-FPM sta ancora elaborando.
Una buona prassi è aumentare anche il valore max_execution_time nella configurazione di PHP (/etc/php/8.2/fpm/php.ini)
max_execution_time = 120per rendere attive le modifiche
systemctl restart php8.2-fpm && nginx -t && systemctl reload nginxScript PHP lento
Aumentare i timeout è una soluzione valida per operazioni legittime e prevedibili (import, report). Tuttavia, se le richieste ordinarie impiegano decine di secondi, il problema è nell’applicazione, non nella configurazione.
Le cause più comuni di script PHP lenti possono essere:
Query SQL non ottimizzate: una SELECT senza indice su una tabella con milioni di righe può richiedere molto tempo. Possiamo verificare se siamo in questa condizione eseguendo la seguente query su MySQL
EXPLAIN SELECT * FROM tabella WHERE campo = 'valore';
+----+-------------+--------+------------+------+---------------+------+---------+------+---------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------+------------+------+---------------+------+---------+------+---------+----------+-------------+
| 1 | SIMPLE | ordini | NULL | ALL | NULL | NULL | NULL | NULL | 2847392 | 10.00 | Using where |
+----+-------------+--------+------------+------+---------------+------+---------+------+---------+----------+-------------+Se il valore della colonna type è ALL (full table scan), manca un indice e questo ha un impatto importante su quasi 3 milioni di righe.
Chiamate a API esterne senza timeout: se lo script attende una risposta da un servizio esterno che non risponde, blocca l’intero processo PHP. Impostare sempre un timeout esplicito nelle chiamate HTTP
$ch = curl_init();
curl_setopt($ch, CURLOPT_TIMEOUT, 10); // massimo 10 secondiServer sotto carico eccessivo
Una causa semplice e tra le più comuni degli errori 504 è la mancanza di risorse sul server upstream. Quando CPU, RAM o disco sono saturi, PHP-FPM impiega molto più tempo a elaborare le richieste, fino a superare il timeout di nginx. Possiamo verificare il carico del server con i seguenti comandi
# Monitoraggio processi e utilizzo CPU/RAM in tempo reale
top
# Versione avanzata di top, con navigazione interattiva e colori
htop
# Utilizzo disco (I/O wait alto è un segnale di disco saturo)
iostat -x 1 5
# Memoria disponibile
free -hSe il server è costantemente al limite, le soluzioni sono:
- Aumentare le risorse (RAM, CPU) del server
- Ottimizzare PHP-FPM riducendo il numero di worker se la RAM è insufficiente
- Implementare una cache (Redis, Memcached, FastCGI cache di Nginx) per ridurre le richieste che arrivano a PHP
Timeout nel backend HTTP (proxy verso Node.js, Apache, altro Nginx)
Se nginx opera come reverse proxy verso un backend HTTP anziché verso PHP-FPM direttamente, le direttive di timeout da modificare sono quelle del modulo proxy
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_read_timeout 120s;
proxy_send_timeout 120s;
proxy_connect_timeout 10s;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}proxy_connect_timeout può rimanere basso (10-15s): se il backend non accetta la connessione in pochi secondi, il problema non è di timeout ma di disponibilità del servizio.
Problemi di rete o DNS tra Nginx e il backend
In ambienti con più server (microservizi, backend remoti), un errore 504 può essere causato da latenza di rete o da un DNS che risolve lentamente l’hostname del backend. Possiamo verificare la connettività con i seguenti comandi
# Test di connessione diretta al backend
curl -v --max-time 10 http://127.0.0.1:3000/
# Se il backend è su un altro host
ping backend-server.domain.local
traceroute backend-server.domain.localPer verificare il tempo di risposta del DNS il comando è
time nslookup backend-server.domain.local
# Esempio di output
Server: 192.168.0.1
Address: 192.168.0.1#53
Name: backend-server.domain.local
Address: 192.168.1.50
real 0m4.823s
user 0m0.012s
sys 0m0.008sSe la risoluzione DNS è lenta, possiamo configurare nginx per usare un resolver esplicito e impostare un TTL breve
server {
listen 443 ssl;
server_name domain.local;
resolver 192.168.0.1 valid=10s;
location / {
proxy_pass http://backend-server.domain.local;
proxy_read_timeout 120s;
}
}Conclusione
L’errore 504 Gateway Timeout su nginx indica che il backend ha impiegato troppo tempo a rispondere o non ha risposto affatto entro il limite configurato. A differenza dell’errore 502, dove il canale di comunicazione è interrotto, nell’errore 504 il processo è avviato ma è troppo lento.
La prima azione da compiere è sempre leggere i log: access.log per capire su quali URL si manifesta il problema, error.log per identificare quale timeout si sta superando. Solo dopo aver individuato la causa possiamo intervenire sulla configurazione.