nginx 504 Gateway Time-out beheben: Ursache finden statt Frist erhöhen
Beim 504 war das Backend erreichbar, es hat nur zu langsam geantwortet. Wie Sie mit dem Zeitprotokoll bestimmen, wo die Zeit verbraucht wird, welche der vielen Fristen wirklich greift und warum eine höhere Frist meist nur den Ausfall verschiebt.
Ein 504 Gateway Time-out ist die geduldigste aller Fehlerseiten. nginx hat die Anfrage angenommen, an das Backend weitergereicht und dann gewartet, bis eine intern gesetzte Frist ablief. Das Backend war die ganze Zeit erreichbar, es hat nur nicht rechtzeitig geantwortet. Genau darin liegt der Unterschied zum Nachbarcode: Beim 502 Bad Gateway antwortet das Backend falsch oder gar nicht, beim 504 antwortet es zu langsam. Diese Anleitung zeigt, wie Sie messen, wo die Zeit wirklich verbraucht wird, welche der vielen Zeitgrenzen überhaupt greift, und warum das Hochsetzen dieser Grenze fast immer die schlechteste der verfügbaren Antworten ist.
Alle Angaben beziehen sich auf Debian 13 (trixie), Debian 12 (bookworm), Ubuntu 24.04 LTS und Ubuntu 22.04 LTS. Die Befehle sind für den Betrieb als root geschrieben, als normaler Benutzer stellen Sie sudo voran. In den Beispielen steht PHP 8.4, ersetzen Sie die Versionsnummer durch die Ihres Systems:
| System | PHP | Dienst | Konfiguration |
|---|---|---|---|
| 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/
Die nginx-Direktiven heißen auf allen vier Systemen gleich. Die Unterschiede stecken auf der PHP- und der Datenbankseite und sind an den betreffenden Stellen vermerkt.
Wer aufgegeben hat, entscheidet über die Suchrichtung
Bevor Sie eine Datei öffnen, beantworten Sie eine Frage: Welche Schicht hat abgebrochen? Der Statuscode verrät das bereits.
| Code | Was passiert ist | Wo Sie suchen |
|---|---|---|
| 500 Internal Server Error | Das Backend hat geantwortet, die Antwort war ein Fehler | Protokoll der Anwendung |
| 502 Bad Gateway | Verbindung kam nicht zustande oder brach ab | Dienst, Socket, Rechte, Abstürze |
| 504 Gateway Time-out | Verbindung stand, die Antwort kam nicht in der Frist | Laufzeit im Backend |
| 408 Request Timeout | Der Besucher hat seine eigene Anfrage nicht rechtzeitig zu Ende geschickt | Uploads, langsame Anschlüsse |
| 499 (nur im Protokoll) | Der Besucher hat abgebrochen, bevor nginx fertig war | Zu langsam, aber unterhalb der Frist |
Die Zeile mit 499 ist die am meisten unterschätzte. Sie ist kein Fehler, sondern ein Frühwarnsystem: Der Besucher hat den Reiter geschlossen, weil ihm die Seite zu lange dauerte. Verschwindet ein 504 nach dem Anheben der Frist und erscheinen stattdessen 499er, ist nichts gelöst.
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head
Feld 9 gilt für das Standardformat combined. Viele 504er bei wenigen 499ern deuten auf einzelne schwere Seiten hin, das umgekehrte Bild auf eine durchgehend zähe Anwendung.
Bevor Sie etwas ändern: der Rückweg
Die Diagnose in den nächsten beiden Abschnitten ist rein lesend. Riskant wird es erst dort, wo Sie Konfigurationen anfassen, und das kann den Betrieb auf drei Wegen stoppen: Eine fehlerhafte nginx-Konfiguration verhindert den Start des Webservers, eine fehlerhafte Pool-Datei den Start von PHP-FPM, und großzügig erhöhte Grenzwerte können den Arbeitsspeicher aufbrauchen. Der letzte Fall ist der unangenehmste, weil der Kernel dann Prozesse beendet, und es trifft nicht zwingend den Verursacher. Erwischt es den SSH-Dienst, ist der Server über das Netz nicht mehr zu bedienen.
Legen Sie deshalb zuerst Kopien an, unterhalb von /root und niemals im Web-Verzeichnis. Der Zeitstempel im Namen ist wichtig, weil man selten nur einmal eingreift und cp -a eine vorhandene Kopie kommentarlos überschreibt:
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)
Der Weg zurück besteht aus drei Zeilen, und die Reihenfolge ist Absicht:
cp -a /root/backups/example.com.2026-09-03-1030 /etc/nginx/sites-available/example.com
nginx -t
systemctl reload nginx
Benutzen Sie reload statt restart, solange es geht. reload übernimmt die neue Konfiguration nur, wenn sie fehlerfrei ist. Ein restart beendet den laufenden Prozess zuerst und lässt Sie bei einem Fehler ohne Webserver zurück.
Antwortet der Server gar nicht mehr, öffnen Sie bei den KVM-Rootservern und Dedicated Servern von KernelHost die VNC-Konsole im Kundenbereich. Sie hängt an der Virtualisierungsschicht beziehungsweise am Anschluss selbst und nicht am Netzwerkstack des Gastsystems, funktioniert also auch dann noch, wenn kein Dienst mehr erreichbar ist. Melden Sie sich dort einmal vorher an und vergewissern Sie sich, dass Sie das root-Passwort kennen. Prüfbefehl nach jedem Eingriff:
systemctl is-active nginx php8.4-fpm
free -m
Die Zeile im Fehlerprotokoll, die den Fall entscheidet
Ein 504 hinterlässt immer eine Spur:
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:"
Entscheidend ist nicht die Fehlernummer 110, die bei jedem 504 gleich lautet, sondern die Phase dahinter:
while connecting to upstream: Der Verbindungsaufbau kam nicht zustande. Bei einem entfernten Backend fast immer ein Paketfilter, der Pakete verwirft statt sie abzulehnen, denn eine Ablehnung käme sofort zurück und ergäbe einen 502.while sending request to upstream: nginx konnte den Anfragekörper nicht loswerden, typisch bei großen Uploads.while reading response header from upstream: der Normalfall. Das Backend hat alles erhalten und rechnet, ohne auch nur die erste Kopfzeile zu schicken.while reading upstream: Die Kopfzeilen kamen, dann stockte der Körper. Das sehen Sie bei Streaming-Antworten und Exporten.
grep -n "upstream timed out" /var/log/nginx/error.log | tail -20
Viele virtuelle Hosts schreiben in ein eigenes Fehlerprotokoll. Wo diese Datei liegt und warum beim Suchen in sites-enabled das große -R nötig ist, steht im Artikel zum 502. Bleibt die Suche leer, obwohl der Browser einen 504 zeigt, stammt der Fehler nicht von diesem nginx.
Wo die Zeit verbraucht wird: messen statt raten
nginx kann pro Anfrage protokollieren, wie lange das Backend gebraucht hat. Das ist der wichtigste Schritt der Fehlersuche, weil er die Frage "Anwendung oder Leitung" ohne Vermutung beantwortet. Im http-Block von /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"';
Im betroffenen Server-Block eine zweite Protokollzeile. Das bestehende Zugriffsprotokoll bleibt unangetastet, nginx schreibt beide:
access_log /var/log/nginx/timing.log kh_timing;
nginx -t
systemctl reload nginx
tail -n 5 /var/log/nginx/timing.log
Nach ein paar Minuten Betrieb holen Sie die langsamsten Anfragen nach oben:
awk '{ t=$3; sub(/^rt=/, "", t); print t, $0 }' /var/log/nginx/timing.log | sort -rn | head -20
| Beobachtung | Deutung | Nächster Schritt |
|---|---|---|
| uct hoch bei lokalem Backend | Der Verbindungsaufbau hakt | Volle Warteschlange am Socket, langsame Namensauflösung |
| uht und urt fast gleich, beide hoch | Das Backend rechnet, bevor es die erste Kopfzeile schickt | Anwendung, Datenbank, fremde Schnittstelle |
| uht klein, urt hoch | Der Kopf kam schnell, der Körper tröpfelt | Streaming, Exporte, Schleifen über viele Datensätze |
| urt klein, rt hoch | Das Backend war schnell, die Zeit ging danach verloren | Leitung des Besuchers, sehr große Antwort |
| Bindestrich statt Zahl | Es war kein Backend beteiligt | Statische Datei oder Abbruch vor der Weitergabe |
Zwei Feinheiten sparen viel Zeit. Mehrere durch Komma getrennte Werte in einem Feld bedeuten, dass die Anfrage an mehr als ein Ziel ging, es gab also einen Wiederholungsversuch. Und der schärfste Hinweis überhaupt: Entspricht die gemessene Dauer auf die Sekunde dem konfigurierten Wert, etwa 60,001 Sekunden bei einer Frist von 60, dann hat die Frist zugeschlagen und nicht das Backend von sich aus aufgegeben. Krumme Werte wie 43,7 Sekunden zeigen, dass etwas anderes gebremst hat.
Welche Zeitgrenze überhaupt greift
nginx kennt für Zeitüberschreitungen ein gutes Dutzend Direktiven, und die häufigste vergeudete Stunde entsteht dadurch, dass jemand die falsche anfasst. Welche greift, hängt daran, welches Modul den location-Block bearbeitet.
| Direktive | Standard | Greift in Blöcken mit | Wirkung beim Ablauf |
|---|---|---|---|
| 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, die entscheidende Frist bei Proxy-Backends |
| fastcgi_connect_timeout | 60s | fastcgi_pass | 504, wie oben, für PHP-FPM |
| fastcgi_send_timeout | 60s | fastcgi_pass | 504, wie oben |
| fastcgi_read_timeout | 60s | fastcgi_pass | 504, die entscheidende Frist bei PHP |
| send_timeout | 60s | überall | kein 504, die Verbindung zum Besucher wird geschlossen |
| client_body_timeout | 60s | überall | 408, nicht 504 |
Die Frist gilt zwischen zwei Lesevorgängen, nicht für die ganze Antwort. Ein Download, der zehn Minuten dauert und dabei ununterbrochen Daten liefert, läuft durch. Ein Backend, das 61 Sekunden schweigt, fliegt raus. Bei stockenden Exporten hilft es deshalb oft mehr, die Anwendung regelmäßig etwas ausgeben zu lassen, als die Frist zu erhöhen.
send_timeout tut nichts gegen einen 504. Diese Frist betrifft die Übertragung zum Besucher. Läuft sie ab, gibt es keine Fehlerseite, sondern einen abgebrochenen Download. Relevant wird sie erst, wenn Sie die Zwischenspeicherung mit proxy_buffering off; abschalten, denn dann bremst ein langsamer Besucher bis ins Backend durch.
nginx akzeptiert auch Direktiven, die im betreffenden Block wirkungslos sind. Ein proxy_read_timeout 300s; in einem PHP-Block mit fastcgi_pass ist syntaktisch einwandfrei, nginx -t meldet syntax is ok, und die Seite bricht weiter nach 60 Sekunden ab. Umgekehrt genauso. Das ist die häufigste Ursache dafür, dass eine erhöhte Frist wirkungslos bleibt. Was wirklich gilt, zeigt die zusammengesetzte Konfiguration:
nginx -T | grep -E "read_timeout|send_timeout|fastcgi_pass|proxy_pass"
Muss ein einzelner Pfad wirklich länger laufen, setzen Sie die Frist genau dort und nirgends sonst. Das Gleichheitszeichen macht daraus eine exakte Übereinstimmung, und die gewinnt gegen den allgemeinen Block location ~ \.php$, der die übrigen PHP-Dateien bearbeitet:
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: warum max_execution_time hier selten greift
Die naheliegende Vermutung lautet, PHP beende ein hängendes Skript von selbst. In genau diesem Fall stimmt das meistens nicht. Zuerst die Messfalle: php -i fragt die Kommandozeilenvariante ab. Die benutzt eine eigene Konfiguration unter /etc/php/8.4/cli/ und läuft ohnehin ohne Laufzeitbegrenzung, sagt über FPM also nichts aus. Maßgeblich sind diese beiden Stellen:
grep -n "^max_execution_time" /etc/php/8.4/fpm/php.ini
grep -rn "max_execution_time" /etc/php/8.4/fpm/pool.d/
In der Pool-Datei kann der Wert mit php_value[max_execution_time] oder php_admin_value[max_execution_time] überschrieben sein. Ein mit php_admin_value gesetzter Wert lässt sich aus der Anwendung heraus per ini_set() nicht mehr ändern. Wenn Ihr Framework die Laufzeit selbst hochsetzt und das plötzlich wirkungslos ist, liegt es daran.
Und jetzt der eigentliche Punkt: Unter Linux zählt Wartezeit in Systemaufrufen nicht mit. Die Uhr läuft nur, solange das Skript selbst rechnet. Wartet es auf eine Datenbankabfrage, auf eine fremde Schnittstelle oder auf das Dateisystem, steht sie still. Ein Skript kann damit zehn Minuten an einer hängenden Abfrage kleben, ohne dass die Laufzeitbegrenzung je zuschlägt. Beendet wird es dann von der nginx-Frist, und das Ergebnis ist der 504.
Daraus folgt eine unbequeme Regel: max_execution_time schützt vor Endlosschleifen im eigenen Code, nicht vor Warten. Die einzige harte Grenze auf PHP-Seite ist request_terminate_timeout in der Pool-Datei, die den Arbeitsprozess unabhängig davon abräumt, woran er hängt. Sie erzeugt allerdings einen 502 und keinen 504. Fristen ordnen Sie von innen nach außen aufsteigend, damit die Schicht zuerst greift, die noch eine verständliche Meldung erzeugen kann. Für rechnende Skripte funktioniert das, für wartende aus dem eben genannten Grund nicht. Dort bleibt nur, die Wartezeit selbst zu begrenzen.
Warum das Hochsetzen der Frist meist die falsche Antwort ist
Rechnen Sie kurz mit. Ein Pool mit pm.max_children = 10 hat zehn Arbeitsprozesse. Eine Seite braucht 90 Sekunden. Zehn gleichzeitige Aufrufe belegen damit anderthalb Minuten lang jeden einzelnen davon. In dieser Zeit bekommt niemand mehr eine PHP-Seite ausgeliefert, auch nicht die Startseite. Aus einer langsamen Unterseite ist ein Ausfall geworden. Drei Mechanismen verstärken das:
- Der Besucher lädt neu. Das erzeugt eine zusätzliche Anfrage, gibt die alte aber nicht frei. PHP bemerkt einen abgebrochenen Besucher erst, wenn das Skript das nächste Mal etwas ausgibt, und ein rechnendes Skript gibt lange nichts aus.
- nginx wiederholt von selbst. Bei einem
upstream-Block mit mehreren Zielen stehtproxy_next_upstreamim Auslieferungszustand auferror timeout. Eine abgelaufene Anfrage geht an den nächsten Server, die teure Abfrage läuft ein zweites Mal. Schreibende Anfragen sind ausgenommen, lesende nicht. Abstellen mitproxy_next_upstream error;. - Überwachung wiederholt ebenfalls. Ein Prüfintervall von 60 Sekunden auf eine Seite, die 90 Sekunden braucht, erzeugt eine Dauerlast, die nie abgearbeitet wird.
Dazu kommt, dass niemand fünf Minuten auf eine Webseite wartet. Eine Frist von 300 Sekunden macht aus einem Problem von einer Minute eines von fünf Minuten, und der Besucher ist längst weg, während der Arbeitsprozess weiterrechnet.
Wie voll es tatsächlich ist, zeigt die Statusseite von PHP-FPM. In der Pool-Datei pm.status_path = /fpm-status setzen und im Server-Block einen nur lokal erreichbaren Zugang schaffen:
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
Achten Sie auf active processes, listen queue und max active processes. Steht listen queue dauerhaft über null, reicht die Zahl der Arbeitsprozesse für die aktuelle Laufzeit der Seiten nicht aus. Das ist der Moment, in dem Sie die Laufzeit senken müssen, nicht die Frist erhöhen.
Die drei üblichen Zeitfresser
Datenbank
In der Mehrzahl der Fälle steckt die Zeit hier. Sehen Sie zuerst nach, was gerade läuft. Auf allen vier Systemen läuft der root-Zugang im Auslieferungszustand über den Unix-Socket, der Befehl kommt also ohne Passwort aus:
mysql -e "SHOW FULL PROCESSLIST;"
Interessant sind die Spalten Time und State. Werte wie Sending data oder Waiting for table metadata lock bei zweistelligen Sekundenwerten sind Ihr Fall. Systematisch suchen Sie mit dem Protokoll langsamer Abfragen, einschaltbar im laufenden Betrieb:
mysql -e "SET GLOBAL slow_query_log = 1; SET GLOBAL long_query_time = 1;"
mysql -e "SHOW VARIABLES LIKE 'slow_query_log_file';"
Den Dateinamen lesen Sie aus der zweiten Ausgabe ab, denn er unterscheidet sich: Debian setzt üblicherweise auf MariaDB und schreibt nach /var/log/mysql/mariadb-slow.log, unter Ubuntu heißt die Datei je nach installiertem Server anders. Nach einigen Minuten auswerten und wieder ausschalten, das Protokoll kostet Schreiblast:
mysqldumpslow -s t /var/log/mysql/mariadb-slow.log | head -30
mysql -e "SET GLOBAL slow_query_log = 0;"
Der Schalter -s t sortiert nach Gesamtzeit. Die teuerste Abfrage prüfen Sie mit EXPLAIN, meist fehlt ein Index auf genau der Spalte, nach der gefiltert oder sortiert wird. SET GLOBAL wirkt sofort, überlebt aber keinen Neustart der Datenbank. Eine Obergrenze je Abfrage gibt es auch auf Datenbankseite: MariaDB kennt max_statement_time in Sekunden, MySQL max_execution_time in Millisekunden, letzteres nur für lesende Abfragen. Damit bekommt Ihre Anwendung einen sauberen Fehler statt eines belegten Arbeitsprozesses.
Fremde Schnittstellen
Fragt Ihre Seite bei jedem Aufruf einen Zahlungsdienstleister oder einen Lizenzserver, wird deren Störung zu Ihrem 504. Messen Sie den Aufruf getrennt, vom Server aus:
curl -o /dev/null -s -w "dns=%{time_namelookup} connect=%{time_connect} ttfb=%{time_starttransfer} total=%{time_total}\n" https://api.example.com/status
Ist bereits dns auffällig, liegt es an der Namensauflösung und nicht an der Gegenstelle, Gegenprobe mit time getent hosts api.example.com. Im Code braucht jeder externe Aufruf eine eigene, knappe Frist: bei cURL sind das CURLOPT_CONNECTTIMEOUT und CURLOPT_TIMEOUT. Wer stattdessen file_get_contents() auf eine Adresse anwendet, landet bei default_socket_timeout aus der php.ini, dort stehen im Auslieferungszustand 60 Sekunden:
grep -n "default_socket_timeout" /etc/php/8.4/fpm/php.ini
Die Faustregel: Die Summe aller externen Fristen einer Anfrage muss unter fastcgi_read_timeout bleiben. Sonst schneidet nginx ab, bevor Ihr Code eine verständliche Fehlerseite ausgeben kann.
Dateisystem und Arbeitsspeicher
Eine volle Platte macht Schreibvorgänge langsam oder unmöglich, und das trifft Sitzungsdateien, Zwischenspeicher und Protokolle gleichzeitig. Prüfen Sie beides, den Platz und die Inodes:
df -h
df -i
Der zweite Befehl ist der oft vergessene. Eine Partition kann bei 40 Prozent Belegung stehen und trotzdem keine Datei mehr aufnehmen, wenn die Inodes aufgebraucht sind. Der zweite Kandidat ist Speichermangel: Lagert das System aus, wird jede Anfrage zäh, ohne dass eine bestimmte Abfrage schuld wäre.
vmstat 1 5
Stehen die Spalten si und so dauerhaft über null, lagert der Kernel ununterbrochen aus und ein, und Sie haben ein Speicherproblem und kein Zeitproblem. Der saubere Umgang damit steht unter Swap einrichten und Out-of-Memory vermeiden. Der dritte Kandidat sind Netzlaufwerke: Ein hängender NFS-Einhängepunkt blockiert jeden Prozess, der ihn anfasst, und dazu gehört auch Ihr Diagnosebefehl. Setzen Sie deshalb eine Frist davor:
timeout 5 df -h
Lange Aufgaben gehören nicht in die Anfrage
Manche Aufgaben dauern nun einmal: ein Jahresbericht, ein Import mit 200.000 Zeilen, eine Bildumwandlung. Der Fehler liegt nicht darin, dass sie lange dauern, sondern darin, dass ein Webserver darauf wartet. Die saubere Form hat drei Teile:
- Die Anfrage legt einen Auftrag an, in einer Tabelle oder einer Warteschlange, und antwortet sofort. Der passende Statuscode ist 202, zusammen mit einer Adresse, unter der sich der Stand abfragen lässt.
- Ein Arbeitsprozess außerhalb von nginx holt die Aufträge und erledigt sie. Er hat keine Frist im Nacken, weil niemand auf ihn wartet.
- Die Oberfläche fragt den Stand ab. Diese Abfrage ist immer schnell, egal wie lange die Aufgabe dauert.
Den Arbeitsprozess betreiben Sie als eigenen Dienst, damit er nach einem Absturz und nach einem Neustart von selbst wiederkommt. Wie eine solche Unit aussieht, zeigt einen systemd-Service erstellen. Für Aufgaben in festen Abständen genügt ein Cronjob, dort brauchen Sie eine Sperre, damit sich zwei Läufe nicht überholen. Mit -n bricht der zweite Lauf sofort ab, statt zu warten:
flock -n /run/lock/kh-worker.lock /usr/bin/php /var/www/html/worker.php
Für die gängigen Frameworks gibt es die Warteschlange fertig, sie muss nur betrieben werden: bei Laravel php artisan queue:work, bei Symfony php bin/console messenger:consume mit dem Namen Ihres Transports. Beides gehört in eine systemd-Unit, nicht in ein Terminalfenster.
Ein Sonderfall verdient eine ausdrückliche Erwähnung: WordPress startet geplante Aufgaben im Auslieferungszustand innerhalb von Besucheranfragen. Ein Besucher zahlt also mit seiner Wartezeit dafür, dass im Hintergrund eine Aktualisierungsprüfung läuft. Die Zeile define('DISABLE_WP_CRON', true); in wp-config.php, oberhalb des Verweises auf wp-settings.php, schaltet das ab. Danach rufen Sie die fälligen Aufgaben regelmäßig selbst auf:
wp cron event run --due-now --path=/var/www/html
Was zwingend synchron bleiben muss, bekommt einen eigenen location-Block mit eigener Frist und zusätzlich eine Begrenzung, damit dieser eine Pfad nicht alle Arbeitsprozesse belegt: limit_conn_zone $binary_remote_addr zone=export:10m; im http-Block und limit_conn export 1; im betreffenden Block. Weitere Versuche bekommen dann eine 503 statt eines belegten Servers.
Häufige Fehler und Lösungen
| Meldung im Wortlaut | Bedeutung und Abhilfe |
|---|---|
upstream timed out (110: Connection timed out) while reading response header from upstream | Der Normalfall. Das Backend rechnet zu lange. Zeitprotokoll auswerten und den Zeitfresser bestimmen, bevor Sie an der Frist drehen. |
upstream timed out (110: Connection timed out) while connecting to upstream | Der Verbindungsaufbau lief in die Frist. Bei einem entfernten Backend fast immer ein Paketfilter, der verwirft statt abzulehnen, bei lokalem PHP-FPM eine volle Warteschlange am Socket. |
upstream timed out (110: Connection timed out) while reading upstream | Die Kopfzeilen kamen, dann stockte der Körper länger als die Frist. Typisch für Exporte, die zwischendurch lange rechnen. |
nginx: [emerg] "fastcgi_read_timeout" directive is not allowed here in /etc/nginx/nginx.conf:12 | Die Direktive steht außerhalb von http, server oder location, meist versehentlich ganz oben in der Datei. |
nginx: [emerg] unknown directive "proxy_read_timout" in /etc/nginx/sites-enabled/example.com:31 | Tippfehler. nginx prüft Namen, nicht Absichten. Die Zeilennummer steht in der Meldung. |
PHP Fatal error: Maximum execution time of 30 seconds exceeded in /var/www/html/export.php on line 42 | Hier hat die PHP-Laufzeitbegrenzung ausnahmsweise gegriffen, das Skript hat also gerechnet und nicht gewartet. Ergibt eine 500 oder eine leere Seite, keinen 504. |
SQLSTATE[HY000]: General error: 1205 Lock wait timeout exceeded; try restarting transaction | Eine andere Transaktion hält die Zeile, die Frist liegt im Auslieferungszustand bei 50 Sekunden. Ursache ist fast immer eine zu lange offene Transaktion. |
cURL error 28: Operation timed out after 60000 milliseconds | Eine fremde Schnittstelle antwortet nicht. Eigene, kürzere Frist im Code setzen, damit Ihre Anwendung die Kontrolle behält. |
504 im Browser, aber die Suche nach upstream timed out bleibt leer | Der 504 stammt nicht von diesem nginx, sondern von einem vorgelagerten Dienst wie einem Loadbalancer oder einem zweiten Proxy. Manche melden dafür einen eigenen Statuscode. |
| Abbruch weiterhin nach exakt 60 Sekunden, obwohl die Frist auf 300 steht | Die geänderte Direktive gehört zum falschen Modul, oder ein anderer Block gewinnt. nginx -T zeigt, was wirklich gilt. |
Woran Sie erkennen, dass es behoben ist
Ein Befehl ohne Fehlermeldung beweist nichts, ein einzelner gelungener Aufruf auch nicht. Vier Nachweise, die zusammen tragen:
- Statuscode und Dauer direkt am Server, damit kein Zwischenspeicher das Ergebnis beschönigt. Erwartet wird 200 und eine Dauer deutlich unterhalb der Frist. Ein 200 nach 58 Sekunden bei einer Frist von 60 ist kein Erfolg, sondern der nächste Ausfall bei etwas mehr Last. Dabei zählt der schlechteste von zwanzig Aufrufen:
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 - Das Fehlerprotokoll bleibt still. Kürzen Sie es vor dem Test mit
truncate -s 0 /var/log/nginx/error.log, lösen Sie die Aufrufe aus, sehen Sie erneut hinein. Eine leere Datei ist der eigentliche Beweis. - Die Warteschlange von PHP-FPM steht bei null. Solange auf der Statusseite unter
listen queueetwas wartet, ist die Ursache nur verschoben. - Ein Neustart ändert nichts. Der am häufigsten übersprungene Punkt. Werte aus
SET GLOBAL, von Hand gestartete Prozesse und selbst angelegte Verzeichnisse unterhalb von/runüberleben ihn nicht. Prüfen Siesystemctl is-enabled nginx php8.4-fpmund starten Sie den Server einmal kontrolliert neu, solange Sie noch hinsehen.
Diesen Neustart samt Konsolenzugriff erledigen Sie bei den KVM-Rootservern und Dedicated Servern von KernelHost im Kundenbereich, auch wenn der Webdienst gerade nichts mehr ausliefert. Die Server stehen im Rechenzentrum maincubes in Frankfurt am Main (TÜV TIER3+ zertifiziert), mit vorgelagerter Filterung im Netz davor.
Kurzcheckliste für den Ernstfall
- Statuscodes im Zugriffsprotokoll zählen. 504, 499 und 502 nebeneinander sagen mehr als jeder einzelne davon.
grep "upstream timed out" /var/log/nginx/error.log, die Phase in der Meldung notieren.- Zeitprotokoll aktivieren,
uct,uhtundurtvergleichen. Passt die Dauer auf die Sekunde zur konfigurierten Frist, hat die Frist zugeschlagen. - Zeitfresser bestimmen: Datenbank, fremde Schnittstelle, Dateisystem oder Speicher.
- Mit
nginx -Tprüfen, welche Frist in diesem Block gilt, bevor Sie eine ändern. - Erst danach entscheiden: beheben, in eine Warteschlange verschieben oder, als letzte Wahl, die Frist für genau diesen einen Pfad anheben.
- Nach dem Fix: Protokoll kürzen, zwanzig Aufrufe messen, Server einmal kontrolliert neu starten.
Häufige Fragen
Was ist der Unterschied zwischen 502 Bad Gateway und 504 Gateway Time-out?
Ich habe fastcgi_read_timeout auf 300 Sekunden gesetzt, die Seite bricht trotzdem nach 60 Sekunden ab. Woran liegt das?
Löst eine höhere Frist das Problem?
Welche Zeile im Fehlerprotokoll gehört zu einem 504?
Warum beendet max_execution_time mein hängendes PHP-Skript nicht?
Wie erkenne ich, ob die Frist zugeschlagen hat oder das Backend von sich aus aufgegeben hat?
Der Browser zeigt 504, aber die Suche nach "upstream timed out" bleibt leer. Woher kommt der Fehler?
Was mache ich mit Aufgaben, die von Natur aus länger dauern als jede sinnvolle Frist?
2026 KernelHost GmbH. Alle Rechte vorbehalten. Diese Anleitung ist urheberrechtlich geschützt. Eine Veröffentlichung auf anderen Webseiten, auch auszugsweise oder in bearbeiteter Form, ist ohne unsere schriftliche Zustimmung nicht gestattet. Zitate mit Quellenangabe und Link sind ausdrücklich willkommen.

