nginx 504 Gateway Time-out: znajdź przyczynę zamiast podnosić limit
Przy błędzie 504 backend był osiągalny, tylko odpowiedział za wolno. Jak z pomocą logu czasów ustalić, gdzie zużywa się czas, który z wielu limitów naprawdę działa i dlaczego wyższy limit najczęściej tylko przesuwa awarię.
Błąd 504 Gateway Time-out to najbardziej cierpliwa ze wszystkich stron błędu. nginx przyjął żądanie, przekazał je do backendu, a potem czekał, aż upłynął ustawiony wewnętrznie limit czasu. Backend był przez cały czas osiągalny, po prostu nie odpowiedział na czas. Właśnie tu leży różnica wobec sąsiedniego kodu: przy 502 Bad Gateway backend odpowiada błędnie albo wcale, przy 504 odpowiada za wolno. Ten poradnik pokazuje, jak zmierzyć, gdzie naprawdę zużywa się czas, który z wielu limitów w ogóle zadziałał i dlaczego podnoszenie tego limitu jest prawie zawsze najgorszą z dostępnych odpowiedzi.
Wszystkie informacje dotyczą systemów Debian 13 (trixie), Debian 12 (bookworm), Ubuntu 24.04 LTS oraz Ubuntu 22.04 LTS. Polecenia napisano z myślą o pracy jako root, jako zwykły użytkownik dopisz z przodu sudo. W przykładach występuje PHP 8.4, zastąp numer wersji tym z twojego systemu:
| System | PHP | Usługa | Konfiguracja |
|---|---|---|---|
| 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/
Dyrektywy nginx nazywają się tak samo we wszystkich czterech systemach. Różnice leżą po stronie PHP oraz bazy danych i są zaznaczone w odpowiednich miejscach.
To, kto się poddał, wyznacza kierunek poszukiwań
Zanim otworzysz jakikolwiek plik, odpowiedz na jedno pytanie: która warstwa przerwała? Kod statusu już to zdradza.
| Kod | Co się stało | Gdzie szukać |
|---|---|---|
| 500 Internal Server Error | Backend odpowiedział, a odpowiedzią był błąd | Log aplikacji |
| 502 Bad Gateway | Połączenie nie doszło do skutku albo się zerwało | Usługa, socket, uprawnienia, awarie |
| 504 Gateway Time-out | Połączenie stało, odpowiedź nie przyszła w wyznaczonym czasie | Czas działania w backendzie |
| 408 Request Timeout | Odwiedzający nie dosłał na czas własnego żądania do końca | Wysyłanie plików, wolne łącza |
| 499 (tylko w logu) | Odwiedzający przerwał, zanim nginx skończył | Za wolno, ale poniżej limitu |
Wiersz z kodem 499 jest najbardziej niedoceniany. To nie błąd, tylko system wczesnego ostrzegania: odwiedzający zamknął kartę, bo strona ładowała się zbyt długo. Jeśli po podniesieniu limitu błąd 504 znika, a w jego miejsce pojawiają się kody 499, nic nie zostało rozwiązane.
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head
Pole numer 9 obowiązuje dla standardowego formatu combined. Dużo kodów 504 przy niewielu 499 wskazuje na pojedyncze ciężkie podstrony, obraz odwrotny na aplikację ociężałą w całości.
Zanim cokolwiek zmienisz: droga powrotna
Diagnostyka z dwóch następnych sekcji tylko czyta. Ryzykownie robi się dopiero tam, gdzie ruszasz konfiguracje, a to potrafi zatrzymać pracę na trzy sposoby: błędna konfiguracja nginx uniemożliwia start serwera WWW, błędny plik puli blokuje start PHP-FPM, a hojnie podniesione limity potrafią wyczerpać pamięć RAM. Ostatni przypadek jest najbardziej dokuczliwy, bo wtedy kernel zabija procesy, i niekoniecznie trafia na sprawcę. Jeśli oberwie usługa SSH, serwera nie da się już obsłużyć przez sieć.
Dlatego najpierw zrób kopie, poniżej katalogu /root i nigdy w katalogu WWW. Znacznik czasu w nazwie jest ważny, bo rzadko kiedy ingeruje się tylko raz, a cp -a nadpisuje istniejącą kopię bez słowa ostrzeżenia:
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)
Droga powrotna to trzy wiersze, a ich kolejność nie jest przypadkowa:
cp -a /root/backups/example.com.2026-09-03-1030 /etc/nginx/sites-available/example.com
nginx -t
systemctl reload nginx
Używaj reload zamiast restart tak długo, jak się da. reload przejmuje nową konfigurację tylko wtedy, gdy jest bezbłędna. restart najpierw kończy działający proces i przy błędzie zostawia cię bez serwera WWW.
Jeśli serwer w ogóle przestanie odpowiadać, przy serwerach root KVM i serwerach dedykowanych od KernelHost otwórz konsolę VNC w panelu klienta. Wisi ona na warstwie wirtualizacji, względnie na samym łączu, a nie na stosie sieciowym systemu gościa, działa więc również wtedy, gdy żadna usługa nie jest już osiągalna. Zaloguj się tam raz zawczasu i upewnij się, że znasz hasło roota. Polecenie kontrolne po każdej ingerencji:
systemctl is-active nginx php8.4-fpm
free -m
Wiersz w logu błędów, który rozstrzyga sprawę
Błąd 504 zawsze zostawia ślad:
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:"
Decydujący nie jest numer błędu 110, który przy każdym 504 brzmi tak samo, tylko faza wymieniona za nim:
while connecting to upstream: nawiązanie połączenia nie doszło do skutku. Przy zdalnym backendzie prawie zawsze odpowiada za to filtr pakietów, który pakiety porzuca, zamiast je odrzucić, bo odrzucenie wróciłoby natychmiast i dałoby błąd 502.while sending request to upstream: nginx nie zdołał przekazać dalej treści żądania, typowe przy dużych plikach wysyłanych na serwer.while reading response header from upstream: przypadek typowy. Backend dostał wszystko i liczy, nie wysyłając nawet pierwszego wiersza nagłówka.while reading upstream: nagłówki przyszły, potem zaciął się korpus odpowiedzi. Zobaczysz to przy odpowiedziach strumieniowych i przy eksportach.
grep -n "upstream timed out" /var/log/nginx/error.log | tail -20
Wiele wirtualnych hostów pisze do własnego logu błędów. Gdzie leży ten plik i dlaczego przy przeszukiwaniu katalogu sites-enabled potrzebne jest wielkie -R, opisuje artykuł o błędzie 502. Jeśli wyszukiwanie nic nie znajduje, choć przeglądarka pokazuje 504, błąd nie pochodzi z tego nginx.
Gdzie zużywa się czas: mierz, nie zgaduj
nginx potrafi zapisywać dla każdego żądania, ile czasu potrzebował backend. To najważniejszy krok diagnostyki, bo bez zgadywania odpowiada na pytanie „aplikacja czy łącze”. W bloku http pliku /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"';
W odpowiednim bloku server dopisz drugi wiersz logowania. Istniejący log dostępu pozostaje nietknięty, nginx pisze do obu:
access_log /var/log/nginx/timing.log kh_timing;
nginx -t
systemctl reload nginx
tail -n 5 /var/log/nginx/timing.log
Po kilku minutach pracy wyciągnij na wierzch najwolniejsze żądania:
awk '{ t=$3; sub(/^rt=/, "", t); print t, $0 }' /var/log/nginx/timing.log | sort -rn | head -20
| Obserwacja | Interpretacja | Następny krok |
|---|---|---|
| wysokie uct przy lokalnym backendzie | Nawiązanie połączenia się zacina | Pełna kolejka na sockecie, wolne rozwiązywanie nazw |
| uht i urt niemal równe, oba wysokie | Backend liczy, zanim wyśle pierwszy wiersz nagłówka | Aplikacja, baza danych, zewnętrzne API |
| uht małe, urt wysokie | Nagłówek przyszedł szybko, korpus sączy się kroplami | Strumieniowanie, eksporty, pętle po wielu rekordach |
| urt małe, rt wysokie | Backend był szybki, czas przepadł później | Łącze odwiedzającego, bardzo duża odpowiedź |
| Myślnik zamiast liczby | Żaden backend nie brał w tym udziału | Plik statyczny albo przerwanie przed przekazaniem dalej |
Dwa szczegóły oszczędzają mnóstwo czasu. Kilka wartości rozdzielonych przecinkiem w jednym polu oznacza, że żądanie poszło do więcej niż jednego celu, doszło więc do ponowienia. I najostrzejsza wskazówka ze wszystkich: jeśli zmierzony czas co do sekundy odpowiada skonfigurowanej wartości, na przykład 60,001 sekundy przy limicie 60, to zadziałał limit, a backend nie poddał się sam z siebie. Niepełne wartości w rodzaju 43,7 sekundy pokazują, że hamowało coś innego.
Który limit czasu w ogóle zadziała
nginx ma na przekroczenia czasu dobre kilkanaście dyrektyw, a najczęściej zmarnowana godzina bierze się stąd, że ktoś rusza niewłaściwą. To, która zadziała, zależy od tego, który moduł obsługuje dany blok location.
| Dyrektywa | Domyślnie | Gdzie działa | Skutek po upływie |
|---|---|---|---|
| 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, decydujący limit przy backendach za proxy |
| fastcgi_connect_timeout | 60s | fastcgi_pass | 504, jak wyżej, dla PHP-FPM |
| fastcgi_send_timeout | 60s | fastcgi_pass | 504, jak wyżej |
| fastcgi_read_timeout | 60s | fastcgi_pass | 504, decydujący limit przy PHP |
| send_timeout | 60s | wszędzie | brak 504, zamykane jest połączenie z odwiedzającym |
| client_body_timeout | 60s | wszędzie | 408, nie 504 |
Limit obowiązuje między dwoma odczytami, a nie dla całej odpowiedzi. Pobieranie, które trwa dziesięć minut i przez cały ten czas nieprzerwanie dostarcza dane, przejdzie bez przeszkód. Backend, który milczy 61 sekund, wylatuje. Przy zacinających się eksportach więcej daje więc regularne wypisywanie czegokolwiek przez aplikację niż podnoszenie limitu.
send_timeout nic nie zmienia przy błędzie 504. Ten limit dotyczy transmisji do odwiedzającego. Gdy upłynie, nie ma strony błędu, tylko przerwane pobieranie. Znaczenia nabiera dopiero wtedy, gdy wyłączysz buforowanie przez proxy_buffering off;, bo wtedy wolny odwiedzający hamuje aż do samego backendu.
nginx przyjmuje też dyrektywy, które w danym bloku nie mają żadnego działania. Wpis proxy_read_timeout 300s; w bloku PHP z fastcgi_pass jest składniowo bez zarzutu, nginx -t zgłasza syntax is ok, a strona dalej przerywa po 60 sekundach. W drugą stronę tak samo. To najczęstsza przyczyna tego, że podniesiony limit pozostaje bez efektu. Co obowiązuje naprawdę, pokazuje złożona konfiguracja:
nginx -T | grep -E "read_timeout|send_timeout|fastcgi_pass|proxy_pass"
Jeśli pojedyncza ścieżka naprawdę musi działać dłużej, ustaw limit dokładnie tam i nigdzie indziej. Znak równości robi z tego dopasowanie dokładne, a ono wygrywa z ogólnym blokiem location ~ \.php$, który obsługuje pozostałe pliki 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: dlaczego max_execution_time rzadko tu działa
Nasuwające się przypuszczenie brzmi tak, że PHP samo zakończy zawieszony skrypt. Akurat w tym przypadku najczęściej się to nie sprawdza. Najpierw pułapka pomiarowa: php -i odpytuje wariant z wiersza poleceń. Ten korzysta z własnej konfiguracji w /etc/php/8.4/cli/ i tak czy inaczej działa bez ograniczenia czasu, o FPM nie mówi więc nic. Miarodajne są te dwa miejsca:
grep -n "^max_execution_time" /etc/php/8.4/fpm/php.ini
grep -rn "max_execution_time" /etc/php/8.4/fpm/pool.d/
W pliku puli wartość może być nadpisana przez php_value[max_execution_time] albo php_admin_value[max_execution_time]. Wartości ustawionej przez php_admin_value nie da się już zmienić z poziomu aplikacji przez ini_set(). Jeśli twój framework sam podnosi czas działania, a nagle jest to bez efektu, przyczyna leży właśnie tutaj.
A teraz sedno sprawy: w Linuksie czas oczekiwania w wywołaniach systemowych się nie liczy. Zegar tyka tylko wtedy, gdy skrypt sam liczy. Kiedy czeka na zapytanie do bazy danych, na zewnętrzne API albo na system plików, zegar stoi. Skrypt może więc wisieć dziesięć minut na zablokowanym zapytaniu, a ograniczenie czasu działania nigdy nie zadziała. Kończy go dopiero limit nginx, a wynikiem jest błąd 504.
Wynika stąd niewygodna zasada: max_execution_time chroni przed nieskończonymi pętlami we własnym kodzie, a nie przed czekaniem. Jedyną twardą granicą po stronie PHP jest request_terminate_timeout w pliku puli, która sprząta proces roboczy niezależnie od tego, na czym ten wisi. Daje jednak błąd 502, a nie 504. Limity ustawiaj rosnąco od środka na zewnątrz, żeby jako pierwsza zadziałała ta warstwa, która potrafi jeszcze wygenerować zrozumiały komunikat. Dla skryptów liczących to działa, dla czekających z wymienionego przed chwilą powodu nie. Tam zostaje tylko ograniczenie samego czasu oczekiwania.
Dlaczego podniesienie limitu to najczęściej zła odpowiedź
Policz szybko razem ze mną. Pula z pm.max_children = 10 ma dziesięć procesów roboczych. Strona potrzebuje 90 sekund. Dziesięć równoczesnych wywołań zajmuje więc przez półtorej minuty każdy z nich. W tym czasie nikt nie dostanie już żadnej strony PHP, nawet strony startowej. Z wolnej podstrony zrobiła się awaria. Wzmacniają to trzy mechanizmy:
- Odwiedzający odświeża stronę. To tworzy dodatkowe żądanie, ale nie zwalnia starego. PHP zauważa przerwanie po stronie odwiedzającego dopiero wtedy, gdy skrypt następnym razem coś wypisze, a skrypt, który liczy, długo nie wypisuje niczego.
- nginx ponawia sam z siebie. Przy bloku
upstreamz kilkoma celamiproxy_next_upstreamstoi domyślnie naerror timeout. Żądanie po upływie limitu idzie do następnego serwera, a kosztowne zapytanie wykonuje się po raz drugi. Żądania zapisujące są z tego wyłączone, odczytujące nie. Wyłączysz to przezproxy_next_upstream error;. - Monitoring też ponawia. Interwał sprawdzania co 60 sekund na stronie, która potrzebuje 90 sekund, tworzy stałe obciążenie, którego nigdy nie da się nadrobić.
Do tego dochodzi fakt, że nikt nie czeka pięciu minut na stronę internetową. Limit 300 sekund robi z problemu jednominutowego problem pięciominutowy, a odwiedzającego dawno już nie ma, podczas gdy proces roboczy dalej liczy.
Jak bardzo jest zapchane naprawdę, pokazuje strona statusu PHP-FPM. W pliku puli ustaw pm.status_path = /fpm-status, a w bloku server utwórz dostęp osiągalny wyłącznie lokalnie:
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
Zwróć uwagę na active processes, listen queue oraz max active processes. Jeśli listen queue stale utrzymuje się powyżej zera, liczba procesów roboczych nie wystarcza przy obecnym czasie działania stron. To moment, w którym trzeba skrócić czas działania, a nie podnosić limit.
Trzy typowe pożeracze czasu
Baza danych
W większości przypadków czas siedzi właśnie tutaj. Sprawdź najpierw, co akurat działa. We wszystkich czterech systemach dostęp roota idzie domyślnie przez socket uniksowy, polecenie obejdzie się więc bez hasła:
mysql -e "SHOW FULL PROCESSLIST;"
Ciekawe są kolumny Time i State. Wartości w rodzaju Sending data albo Waiting for table metadata lock przy dwucyfrowej liczbie sekund to twój przypadek. Systematycznie szukasz przez log wolnych zapytań, który da się włączyć w trakcie pracy:
mysql -e "SET GLOBAL slow_query_log = 1; SET GLOBAL long_query_time = 1;"
mysql -e "SHOW VARIABLES LIKE 'slow_query_log_file';"
Nazwę pliku odczytasz z drugiego wyniku, bo bywa różna: Debian stawia zwykle na MariaDB i pisze do /var/log/mysql/mariadb-slow.log, a w Ubuntu plik nazywa się inaczej, zależnie od zainstalowanego serwera. Po kilku minutach przeanalizuj wynik i wyłącz log z powrotem, bo kosztuje on obciążenie zapisem:
mysqldumpslow -s t /var/log/mysql/mariadb-slow.log | head -30
mysql -e "SET GLOBAL slow_query_log = 0;"
Przełącznik -s t sortuje po łącznym czasie. Najdroższe zapytanie sprawdzisz przez EXPLAIN, najczęściej brakuje indeksu dokładnie na tej kolumnie, po której odbywa się filtrowanie albo sortowanie. SET GLOBAL działa natychmiast, ale nie przeżywa restartu bazy danych. Górny limit na pojedyncze zapytanie istnieje również po stronie bazy: MariaDB zna max_statement_time w sekundach, MySQL max_execution_time w milisekundach, przy czym to drugie tylko dla zapytań odczytujących. Dzięki temu twoja aplikacja dostaje czysty błąd zamiast zajętego procesu roboczego.
Zewnętrzne interfejsy API
Jeśli twoja strona przy każdym wywołaniu odpytuje operatora płatności albo serwer licencji, jego awaria staje się twoim błędem 504. Zmierz to wywołanie osobno, z poziomu serwera:
curl -o /dev/null -s -w "dns=%{time_namelookup} connect=%{time_connect} ttfb=%{time_starttransfer} total=%{time_total}\n" https://api.example.com/status
Jeśli już dns rzuca się w oczy, winne jest rozwiązywanie nazw, a nie druga strona, próba kontrolna przez time getent hosts api.example.com. W kodzie każde zewnętrzne wywołanie potrzebuje własnego, krótkiego limitu: przy cURL są to CURLOPT_CONNECTTIMEOUT oraz CURLOPT_TIMEOUT. Kto zamiast tego wywołuje file_get_contents() na adresie URL, ląduje przy default_socket_timeout z pliku php.ini, gdzie domyślnie stoi 60 sekund:
grep -n "default_socket_timeout" /etc/php/8.4/fpm/php.ini
Zasada praktyczna: suma wszystkich zewnętrznych limitów w obrębie jednego żądania musi pozostać poniżej fastcgi_read_timeout. Inaczej nginx utnie połączenie, zanim twój kod zdąży wypisać zrozumiałą stronę błędu.
System plików i pamięć RAM
Pełny dysk spowalnia zapisy albo je uniemożliwia, a dotyka to jednocześnie plików sesji, cache'u i logów. Sprawdź jedno i drugie, wolne miejsce oraz inody:
df -h
df -i
Drugie polecenie bywa zapominane najczęściej. Partycja może być zajęta w 40 procentach i mimo to nie przyjmie już żadnego pliku, gdy inody się wyczerpią. Drugi kandydat to brak pamięci: gdy system zaczyna odkładać dane do swapu, każde żądanie robi się ociężałe, choć żadne konkretne zapytanie nie ponosi winy.
vmstat 1 5
Jeśli kolumny si i so stale stoją powyżej zera, kernel bez przerwy odkłada dane do swapu i wciąga je z powrotem, a ty masz problem z pamięcią, a nie z czasem. Jak sobie z tym czysto poradzić, opisuje konfiguracja swapu i unikanie błędu Out-of-Memory. Trzeci kandydat to dyski sieciowe: zawieszony punkt montowania NFS blokuje każdy proces, który go dotknie, a należy do nich również twoje polecenie diagnostyczne. Dlatego postaw przed nim limit czasu:
timeout 5 df -h
Długie zadania nie należą do żądania
Niektóre zadania po prostu trwają: raport roczny, import z 200 000 wierszy, konwersja obrazów. Błąd nie polega na tym, że trwają długo, tylko na tym, że czeka na nie serwer WWW. Czysta forma ma trzy części:
- Żądanie zakłada zlecenie, w tabeli albo w kolejce, i odpowiada natychmiast. Pasujący kod statusu to 202, razem z adresem, pod którym da się sprawdzić stan.
- Proces roboczy poza nginx pobiera zlecenia i je wykonuje. Nie ma limitu na karku, bo nikt na niego nie czeka.
- Interfejs odpytuje o stan. To zapytanie jest zawsze szybkie, niezależnie od tego, jak długo trwa samo zadanie.
Proces roboczy prowadź jako osobną usługę, żeby wracał sam po awarii i po restarcie. Jak wygląda taka jednostka, pokazuje tworzenie usługi systemd. Do zadań w stałych odstępach wystarczy cronjob, tam potrzebujesz blokady, żeby dwa uruchomienia się nie wyprzedziły. Z -n drugie uruchomienie przerywa natychmiast, zamiast czekać:
flock -n /run/lock/kh-worker.lock /usr/bin/php /var/www/html/worker.php
Popularne frameworki mają kolejkę gotową, trzeba ją tylko uruchomić: w Laravelu php artisan queue:work, w Symfony php bin/console messenger:consume z nazwą twojego transportu. Jedno i drugie należy do jednostki systemd, a nie do okna terminala.
Jeden szczególny przypadek zasługuje na wyraźną wzmiankę: WordPress domyślnie uruchamia zaplanowane zadania w obrębie żądań odwiedzających. Odwiedzający płaci więc swoim czasem oczekiwania za to, że w tle leci sprawdzanie aktualizacji. Wiersz define('DISABLE_WP_CRON', true); w pliku wp-config.php, powyżej odwołania do wp-settings.php, to wyłącza. Potem sam regularnie wywołujesz zadania, których termin nadszedł:
wp cron event run --due-now --path=/var/www/html
To, co koniecznie musi pozostać synchroniczne, dostaje własny blok location z własnym limitem, a dodatkowo ograniczenie, żeby ta jedna ścieżka nie zajęła wszystkich procesów roboczych: limit_conn_zone $binary_remote_addr zone=export:10m; w bloku http oraz limit_conn export 1; w odpowiednim bloku. Kolejne próby dostaną wtedy kod 503 zamiast zajętego serwera.
Częste błędy i rozwiązania
| Komunikat w oryginalnym brzmieniu | Znaczenie i sposób naprawy |
|---|---|
upstream timed out (110: Connection timed out) while reading response header from upstream | Przypadek typowy. Backend liczy za długo. Przeanalizuj log czasów i ustal pożeracza czasu, zanim zaczniesz kręcić limitem. |
upstream timed out (110: Connection timed out) while connecting to upstream | Nawiązanie połączenia wpadło w limit. Przy zdalnym backendzie prawie zawsze filtr pakietów, który porzuca zamiast odrzucać, przy lokalnym PHP-FPM pełna kolejka na sockecie. |
upstream timed out (110: Connection timed out) while reading upstream | Nagłówki przyszły, potem korpus zaciął się na dłużej niż limit. Typowe dla eksportów, które w międzyczasie długo liczą. |
nginx: [emerg] "fastcgi_read_timeout" directive is not allowed here in /etc/nginx/nginx.conf:12 | Dyrektywa stoi poza blokiem http, server albo location, najczęściej przez przypadek na samej górze pliku. |
nginx: [emerg] unknown directive "proxy_read_timout" in /etc/nginx/sites-enabled/example.com:31 | Literówka. nginx sprawdza nazwy, a nie intencje. Numer wiersza stoi w komunikacie. |
PHP Fatal error: Maximum execution time of 30 seconds exceeded in /var/www/html/export.php on line 42 | Tutaj wyjątkowo zadziałało ograniczenie czasu działania PHP, skrypt liczył więc, a nie czekał. Daje kod 500 albo pustą stronę, a nie 504. |
SQLSTATE[HY000]: General error: 1205 Lock wait timeout exceeded; try restarting transaction | Inna transakcja trzyma ten wiersz, limit wynosi domyślnie 50 sekund. Przyczyną jest prawie zawsze transakcja otwarta zbyt długo. |
cURL error 28: Operation timed out after 60000 milliseconds | Zewnętrzne API nie odpowiada. Ustaw w kodzie własny, krótszy limit, żeby twoja aplikacja zachowała kontrolę. |
504 w przeglądarce, ale wyszukiwanie upstream timed out nic nie znajduje | Ten 504 nie pochodzi z tego nginx, tylko z usługi ustawionej przed nim, na przykład load balancera albo drugiego proxy. Niektóre zgłaszają w takim wypadku własny kod statusu. |
| Przerwanie dalej po dokładnie 60 sekundach, mimo że limit stoi na 300 | Zmieniona dyrektywa należy do niewłaściwego modułu albo wygrywa inny blok. nginx -T pokazuje, co obowiązuje naprawdę. |
Po czym poznasz, że problem jest naprawiony
Polecenie bez komunikatu o błędzie niczego nie dowodzi, pojedyncze udane wywołanie też nie. Cztery dowody, które razem się bronią:
- Kod statusu i czas trwania mierzone bezpośrednio na serwerze, żeby żaden cache nie upiększył wyniku. Spodziewany wynik to kod 200 i czas wyraźnie poniżej limitu. Kod 200 po 58 sekundach przy limicie 60 to nie sukces, tylko kolejna awaria przy odrobinę większym obciążeniu. Liczy się przy tym najgorsze z dwudziestu wywołań:
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 - Log błędów milczy. Przed testem opróżnij go poleceniem
truncate -s 0 /var/log/nginx/error.log, wywołaj żądania, a potem zajrzyj do niego ponownie. Pusty plik to właściwy dowód. - Kolejka PHP-FPM stoi na zerze. Dopóki na stronie statusu pod
listen queuecoś czeka, przyczyna została tylko przesunięta. - Restart niczego nie zmienia. Punkt pomijany najczęściej. Wartości z
SET GLOBAL, procesy uruchomione ręcznie i katalogi utworzone samodzielnie poniżej/runtego nie przeżyją. Sprawdźsystemctl is-enabled nginx php8.4-fpmi zrestartuj serwer raz w kontrolowany sposób, dopóki jeszcze na to patrzysz.
Ten restart razem z dostępem do konsoli wykonasz przy serwerach root KVM i serwerach dedykowanych od KernelHost w panelu klienta, nawet jeśli usługa WWW akurat niczego już nie wydaje. Serwery stoją w centrum danych maincubes we Frankfurcie nad Menem (certyfikat TÜV TIER3+), a ruch przechodzi przez filtrowanie w sieci przed nimi.
Krótka lista kontrolna na sytuację awaryjną
- Policz kody statusu w logu dostępu. Kody 504, 499 i 502 zestawione obok siebie mówią więcej niż każdy z osobna.
grep "upstream timed out" /var/log/nginx/error.log, zanotuj fazę podaną w komunikacie.- Włącz log czasów, porównaj
uct,uhtiurt. Jeśli czas co do sekundy pasuje do skonfigurowanego limitu, zadziałał limit. - Ustal pożeracza czasu: baza danych, zewnętrzne API, system plików albo pamięć.
- Poleceniem
nginx -Tsprawdź, który limit obowiązuje w tym bloku, zanim któryś zmienisz. - Dopiero potem decyduj: naprawić, przenieść do kolejki albo, w ostateczności, podnieść limit dokładnie dla tej jednej ścieżki.
- Po poprawce: opróżnij log, zmierz dwadzieścia wywołań, raz w kontrolowany sposób zrestartuj serwer.
Najczęstsze pytania
Jaka jest różnica między 502 Bad Gateway a 504 Gateway Time-out?
Ustawiłem fastcgi_read_timeout na 300 sekund, a strona i tak przerywa po 60 sekundach. Z czego to wynika?
Czy wyższy limit rozwiązuje problem?
Który wiersz w logu błędów należy do błędu 504?
Dlaczego max_execution_time nie kończy mojego zawieszonego skryptu PHP?
Jak poznam, czy zadziałał limit, czy backend poddał się sam z siebie?
Przeglądarka pokazuje 504, ale wyszukiwanie "upstream timed out" nic nie znajduje. Skąd bierze się ten błąd?
Co zrobić z zadaniami, które z natury trwają dłużej niż każdy sensowny limit?
2026 KernelHost GmbH. Wszelkie prawa zastrzeżone. Ten poradnik jest chroniony prawem autorskim. Publikowanie go w innych serwisach, w całości, we fragmentach lub w zmienionej formie, wymaga naszej pisemnej zgody. Cytaty z podaniem źródła i z linkiem są jak najbardziej mile widziane.

