Решение проблем

504 Gateway Time-out в nginx: как найти медленный бэкенд и исправить ошибку

504 значит, что nginx ждал ответа от PHP-FPM, Node или другого бэкенда и не дождался. Читаем лог, увеличиваем нужный таймаут и чиним медленный запрос.

Опубликовано ~10 мин чтения

Ошибка 504 Gateway Time-out от nginx значит, что nginx передал запрос вашему бэкенду (PHP-FPM, приложению на Node, gunicorn или другому серверу), бэкенд его принял, но ответ не пришёл, пока не истёк таймаут чтения. По умолчанию это 60 секунд. Найдите медленный запрос в логе ошибок, устраните то, что его тормозит, и увеличивайте proxy_read_timeout или fastcgi_read_timeout только для маршрутов, которым действительно нужно долго работать.

Debian · Ubuntu · ищем причину
sudo tail -n 50 /var/log/nginx/error.log

# "upstream timed out (110: Connection timed out) while reading response header from upstream"
#   -> бэкенд слишком медленный: см. «Почему бэкенд медленный?» ниже
# "upstream timed out (110: Connection timed out) while connecting to upstream"
#   -> бэкенд недоступен: файрвол, неверный IP или порт
nginx · длинный таймаут только для одного маршрута
location /reports/ {
    proxy_pass http://127.0.0.1:3000;
    proxy_read_timeout 300s;
}
Debian · Ubuntu · применяем
sudo nginx -t && sudo systemctl reload nginx

nginx свои сообщения не переводит, поэтому и страница ошибки, и строки в логе выглядят одинаково на любом сервере, с какой бы локалью он ни работал.

Чем 502 отличается от 504?

  • 502 Bad Gateway: nginx не получил пригодного ответа. Соединение отклонено или сброшено, сокета не существует, или бэкенд закрыл соединение, так и не отправив ответ. Подробности — в статье nginx 502 Bad Gateway.
  • 504 Gateway Timeout: соединение установилось, но ответ не пришёл вовремя. nginx перестал ждать.

Таймаут может закончиться и кодом 502. Если бэкенд первым применяет собственный лимит, например таймаут воркера в gunicorn или request_terminate_timeout в PHP-FPM, он убивает запрос и закрывает соединение. Тогда nginx пишет в лог upstream prematurely closed … или Connection reset by peer и отвечает 502. Так что если для одной и той же медленной страницы вы видите оба кода, проблема одна: бэкенд оборвал запрос раньше, чем nginx.

Какой таймаут срабатывает?

У каждого типа бэкенда свой набор из трёх таймаутов, и все они по умолчанию равны 60 секундам:

  • proxy_pass (Node, Python за gunicorn или uvicorn, другой веб-сервер): proxy_connect_timeout, proxy_send_timeout, proxy_read_timeout.
  • fastcgi_pass (PHP-FPM): fastcgi_connect_timeout, fastcgi_send_timeout, fastcgi_read_timeout.
  • У uwsgi_pass и grpc_pass та же тройка, только со своим префиксом.

Какой из них сработал, видно по концу строки в логе ошибок:

  • while connecting to upstream: таймаут подключения. Бэкенд даже не принял соединение. В документации nginx отмечено, что этот таймаут обычно не может превышать 75 секунд.
  • while sending request to upstream: таймаут отправки. Бэкенд перестал читать тело запроса; такое бывает редко.
  • while reading response header from upstream: таймаут чтения, и это самый частый случай. Бэкенд обрабатывает запрос и ещё не начал отвечать.
  • while reading upstream: ответ начался, а потом застрял на середине. К этому моменту nginx уже отправил строку статуса, поэтому посетитель получает обрезанную страницу, а не 504.

Если в одном блоке upstream несколько серверов, таймаут ещё и заставляет nginx попробовать следующий, потому что proxy_next_upstream (и fastcgi_next_upstream) по умолчанию равен error timeout. Посетитель может прождать несколько таймаутов подряд, прежде чем получит 504, а в логе будет по одному значению времени на каждую попытку, через запятую. Ограничьте это через proxy_next_upstream_tries или proxy_next_upstream_timeout. POST-запросы дальше не передаются, если уже дошли до сервера.

Как исправить 504 с PHP-FPM?

PHP-запрос проходит три лимита, заданных в трёх разных файлах, и они должны быть согласованы:

  • nginx fastcgi_read_timeout: сколько nginx ждёт FPM. По умолчанию 60 с.
  • PHP max_execution_time в php.ini: по умолчанию 30 с для веб-запросов. В Linux он учитывает только время, пока выполняется сам скрипт. Ожидание запроса к базе, потока или внешнего HTTP-вызова не считается, поэтому скрипт, который в основном ждёт, может работать намного дольше этого значения.
  • FPM request_terminate_timeout в конфигурации пула: реальное (wall-clock) время, после которого FPM убивает воркер. По умолчанию 0, то есть выключен.

Расставьте их так, чтобы первым срабатывал самый внутренний лимит: max_execution_time меньше request_terminate_timeout, и оба меньше fastcgi_read_timeout. Тогда PHP останавливает зациклившийся скрипт с понятной ошибкой в собственном логе, FPM убивает всё, что зависло на вводе-выводе, а nginx никогда не ждёт дольше их обоих. Приложения могут переопределить max_execution_time через set_time_limit(), и плагины WordPress часто так делают.

/etc/php/8.3/fpm/php.ini
max_execution_time = 60
/etc/php/8.3/fpm/pool.d/www.conf
request_terminate_timeout = 90s
Debian · Ubuntu
sudo php-fpm8.3 -t && sudo systemctl restart php8.3-fpm

Версию в этих путях показывает php -v, здесь это 8.3. На Rocky и AlmaLinux те же две строки идут в /etc/php.ini и /etc/php-fpm.d/www.conf:

Rocky · AlmaLinux
sudo php-fpm -t && sudo systemctl restart php-fpm
nginx · Debian · Ubuntu
location ~ \.php$ {
    include snippets/fastcgi-php.conf;
    fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    fastcgi_read_timeout 120s;
}

Debian 12 поставляется с PHP 8.2, Debian 13 — с 8.4, Ubuntu 22.04 — с 8.1, Ubuntu 24.04 — с 8.3, Ubuntu 26.04 — с 8.5. Берите пути и имя службы, которые соответствуют вашей версии.

Как исправить 504 с Node.js, gunicorn или uWSGI?

  • Node.js: HTTP-сервер Node по умолчанию не ограничивает по времени медленный обработчик, поэтому первым лимитом для медленного запроса обычно становится proxy_read_timeout в nginx. Увеличьте его для нужного location, как в быстром решении выше.
  • gunicorn: его таймаут воркера --timeout по умолчанию равен 30 секундам и убивает воркер, а nginx сообщает об этом как о 502. Увеличивайте таймаут gunicorn и proxy_read_timeout вместе, причём у nginx — чуть больше.
  • uWSGI: ту же роль играет его опция harakiri. Согласуйте её с uwsgi_read_timeout.
  • Ещё один nginx или Apache за этим: у каждого прокси в цепочке свой таймаут. Побеждает самый короткий.

Почему бэкенд медленный?

Увеличенный таймаут лишь прячет проблему, пока не вырастет трафик. Сначала выясните, какие запросы медленные. Добавьте в access-лог время обработки:

nginx · блок http
log_format timed '$remote_addr "$request" $status '
                 'rt=$request_time uct=$upstream_connect_time '
                 'uht=$upstream_header_time urt=$upstream_response_time';
access_log /var/log/nginx/timed.log timed;

uct — время подключения к бэкенду, uht — время до получения заголовков его ответа (именно здесь возникает большинство 504), urt — весь ответ бэкенда; - значит, что этот шаг так и не завершился. Новый файл лежит рядом с обычным access.log, который сохраняет свой формат. Блок server с собственной строкой access_log не наследует ни то, ни другое, поэтому добавьте строку с timed и туда. Затем обратитесь к бэкенду напрямую, в обход nginx, чтобы убедиться:

на сервере · замер самого бэкенда
curl -o /dev/null -s -w 'total %{time_total}s\n' http://127.0.0.1:3000/slow-page

Медленные запросы к базе данных

Одна из самых частых причин. Недостающий индекс или запрос, который растёт вместе с таблицей, за несколько месяцев превращают быструю страницу в медленную. Посмотрите, что выполняется прямо сейчас:

MariaDB · MySQL · в sudo mysql
SHOW FULL PROCESSLIST;
SET GLOBAL slow_query_log = 1;
SET GLOBAL long_query_time = 1;        -- медленнее 1 с; действует для новых подключений
SHOW VARIABLES LIKE 'slow_query_log_file';
PostgreSQL · в sudo -u postgres psql
SELECT pid, now() - query_start AS runtime, query
FROM pg_stat_activity WHERE state = 'active' ORDER BY runtime DESC;

У PHP-FPM закончились воркеры

Если заняты все воркеры, новые запросы ждут в очереди, и это ожидание идёт в зачёт таймаута nginx. FPM сообщает об этом в своём логе (/var/log/php8.3-fpm.log в Debian и Ubuntu, /var/log/php-fpm/error.log в Rocky):

лог FPM
WARNING: [pool www] server reached pm.max_children setting (5), consider raising it

В конфигурации пула, которую поставляют Debian и Ubuntu, стоит pm.max_children = 5, а это мало для настоящего сайта. В Rocky и Alma — 50. Проверьте, что у вас, командой grep -E '^pm' /etc/php/8.3/fpm/pool.d/www.conf (или /etc/php-fpm.d/www.conf). Подбирайте pm.max_children по памяти: объём RAM, который можно отдать PHP, делённый на средний размер одного воркера.

Debian · Ubuntu · средний размер воркера FPM
ps -o rss= -C php-fpm8.3 | awk '{s+=$1; n++} END {print s/n/1024 " MB"}'
# на Rocky: -C php-fpm

Чтобы найти сами медленные скрипты, включите slow log FPM в конфигурации пула: request_slowlog_timeout = 5s и slowlog = /var/log/php8.3-fpm-slow.log (в Rocky и Alma стандартный пул уже направляет slowlog в /var/log/php-fpm/www-slow.log). После этого каждый запрос дольше пяти секунд оставляет стек вызовов PHP, по которому видно, где он ждал.

Процессор, steal time или нехватка памяти

Если медленно всё, а не отдельные страницы, смотрите на саму машину. Высокая загрузка CPU или высокий st (steal) в top разобраны в статье высокая нагрузка на CPU на VPS (англ.). Активный своп (ненулевые колонки si и so в vmstat 1) замедляет каждый запрос, а бесследно исчезающие воркеры указывают на OOM killer. Подробнее — в статье процесс убит OOM killer (англ.).

Медленные внешние вызовы

Запрос, который обращается к платёжному API, почтовому серверу или другому сервису, ждёт столько, сколько отвечает этот сервис, а у HTTP-клиентов таймаута по умолчанию часто нет. Задайте его явно в коде (CURLOPT_TIMEOUT в curl, аргумент timeout= в Python-библиотеке requests, AbortSignal в Node), чтобы одна медленная зависимость быстро завершалась ошибкой, а не держала воркер минуту.

Что делать, если запрос и правда выполняется несколько минут?

Экспорту, генерации отчётов, импорту, обработке видео и массовой рассылке не место в запросе, ответа на который кто-то ждёт. Сразу отвечайте 202 Accepted с идентификатором задачи, выполняйте её в фоновом воркере, а клиент пусть опрашивает результат или получает уведомление о готовности. Это умеют очереди Laravel, Celery, RQ, BullMQ или небольшая служба systemd, которая берёт задачи из таблицы. Таймаут в десять минут лишь переносит сбой на следующий уровень и всё это время держит воркер занятым.

Если медленная страница одинакова для всех посетителей — отчёт, лента или дашборд, — кешируйте её: с proxy_cache или fastcgi_cache в nginx либо собственным кешем приложения бэкенд ждёт только первый запрос после истечения кеша.

Что значит «while connecting to upstream»?

while connecting to upstream значит, что на TCP-рукопожатие никто не ответил. Остановленный бэкенд на той же машине обычно даёт быстрый Connection refused, а это 502. Таймаут же указывает на:

  • Файрвол, который отбрасывает пакеты между nginx и бэкендом на другом хосте. Правила drop заставляют соединения ждать до таймаута, правила reject дают отказ сразу.
  • Неверный или устаревший IP, например старый адрес в proxy_pass или бэкенд, переехавший на другой хост.
  • Сеть Docker: IP контейнера, сменившийся после перезапуска. Проксируйте на опубликованный порт на 127.0.0.1 или поместите nginx в ту же сеть Docker и обращайтесь по имени сервиса.
на сервере с nginx · проверка пути до бэкенда
curl -v --connect-timeout 5 http://10.0.0.5:8080/

Подставьте адрес и порт из своего блока proxy_pass или upstream. Таймаут здесь (curl: (28) Failed to connect … Timeout was reached) на той же машине, где работает nginx, подтверждает, что пакеты отбрасываются по пути.

Это 524 от Cloudflare или 504 от nginx?

За Cloudflare работают два таймера. Cloudflare по умолчанию ждёт ответа вашего исходного сервера (origin) 125 секунд. В старых руководствах написано 100, но в документации Cloudflare сейчас указано 125. Если origin к этому времени не ответил, посетители получают собственную ошибку Cloudflare 524 «A timeout occurred», что бы ни разрешал nginx. Поднять этот лимит можно только на тарифе Enterprise, до 6000 секунд.

  • 504 за Cloudflare значит, что первым сдался nginx (или что-то за ним). Всё написанное выше применимо.
  • 524 значит, что ваш origin ещё работал, когда Cloudflare перестал ждать. Увеличение proxy_read_timeout сверх 125 секунд для проксируемого трафика ничего не меняет; решения для этого случая — в статье ошибка Cloudflare 524 (англ.).

Для действительно долгого запроса вынесите работу в очередь, как описано выше, или отдавайте этот эндпоинт с отдельного имени хоста, запись которого работает в режиме DNS only (серое облако): такой трафик Cloudflare не проксирует. Если вы как раз настраиваете такую запись, пригодится статья как направить домен на VPS (англ.).

Увеличили таймаут, а 504 осталась?

  • Изменение не действует. nginx -t выдал ошибку, nginx не перезагрузили, или запрос обслуживает другой блок server или location. sudo nginx -T | grep -n timeout покажет все таймауты в загруженной конфигурации.
  • Другой прокси в цепочке всё ещё работает со своим значением по умолчанию: балансировщик нагрузки (у Application Load Balancer в AWS, например, таймаут простоя по умолчанию 60 секунд), второй nginx впереди или ingress-контроллер.
  • Бэкенд не отвечает никогда. Взаимная блокировка или запрос, ждущий снятия блокировки, упрётся в таймаут при любом лимите. Смотрите slow log и список процессов базы данных прямо во время сбоя.

Если nginx вместо этого отвечает 403 Forbidden, дело в правах доступа — это разобрано в статье nginx 403 Forbidden (англ.). А если медленным звеном окажется PHP или Node, которым просто не хватает процессора, тариф Hi-CPU KVM даст PHP-FPM и Node вычислительный запас, чтобы ответ уверенно укладывался в таймаут.

Частые вопросы

Почему возникает ошибка 504 Gateway Time-out в nginx?

nginx передал запрос бэкенду — PHP-FPM, Node или gunicorn — и не получил ответа за время таймаута чтения, по умолчанию 60 секунд. Обычные причины: медленные запросы к базе данных, бэкенд без свободных воркеров, нехватка CPU или памяти, медленный вызов внешнего API. Если 504 показывает чужой сайт, устранить её может только его владелец: ошибка возникает на сервере.

Как увеличить таймаут в nginx при ошибке 504?

Задайте proxy_read_timeout для бэкендов за proxy_pass или fastcgi_read_timeout для PHP-FPM, лучше только в том location, которому это нужно, например «proxy_read_timeout 300s;», затем выполните «sudo nginx -t && sudo systemctl reload nginx». Для PHP поднимите до нужного значения и max_execution_time с request_terminate_timeout, оставив оба чуть ниже значения в nginx, чтобы PHP останавливал зациклившийся скрипт раньше, чем сдастся nginx.

Чем ошибка 502 отличается от 504 в nginx?

502 Bad Gateway значит, что nginx не получил пригодного ответа: соединение отклонено или сброшено, либо бэкенд закрыл его раньше времени. 504 Gateway Time-out значит, что соединение установилось, но ответ не пришёл вовремя. Какой из случаев у вас, видно по строке в /var/log/nginx/error.log.

Читайте также

Все статьи раздела