Что делать, если

Ошибка 504 Gateway Timeout: причины и что делать

Ошибка 504 Gateway Timeout: что делать — какой таймаут nginx, Apache или Cloudflare сработал, как найти долгий запрос по логам и slowlog, когда таймаут поднимать.

Ошибка 504 Gateway Timeout означает одно: прокси (nginx, Apache, балансировщик или CDN) отправил запрос на бэкенд, ждал ответа положенное время и не дождался. Бэкенд при этом жив — иначе была бы 502, — он просто не успел ответить за отведённые секунды. Поэтому «что делать» сводится к двум шагам: посмотреть, какой именно таймаут сработал, и найти, чем был занят бэкенд всё это время.

Чинить 504 повышением таймаута — самый частый и самый вредный рефлекс. Запрос, который отвечает 90 секунд вместо 60, всё равно занимает воркер, и пока таких запросов несколько, остальные посетители стоят в очереди и получают ту же 504. Ниже — где искать долгий запрос, какие цифры в логах на это указывают и в каких случаях таймаут всё-таки стоит поднять.

Если сомневаетесь, кто именно вернул ошибку — CDN, nginx или само приложение, — таблица «кто отдал» есть в статье про ошибку 502 Bad Gateway; для 504 она работает точно так же, поэтому здесь не повторяется.

Какой таймаут сработал

У каждого прокси свой набор директив, и все они по умолчанию близки к минуте. Первое действие — сверить, что стоит у вас, с тем, сколько на самом деле длится запрос.

ПроксиДирективаПо умолчаниюЧто ограничивает
nginx → HTTP-бэкендproxy_read_timeout60 sПауза между двумя чтениями ответа, а не общее время
nginx → PHP-FPMfastcgi_read_timeout60 sТо же для FastCGI
nginx → uWSGIuwsgi_read_timeout60 sТо же для uWSGI
nginx, установление соединенияproxy_connect_timeout60 s (не больше 75 s)Только TCP-handshake; при его истечении тоже 504
Apache mod_proxyProxyTimeout или timeout= в ProxyPassзначение Timeout, 60 sОжидание ответа бэкенда
Apache mod_fcgidFcgidIOTimeout40 sОжидание ответа FastCGI-процесса
Cloudflareне настраивается на обычных тарифах100 sВозвращает код 524, а не 504

Cloudflare — отдельный случай. Если origin молчит дольше 100 секунд, посетитель увидит 524 «A timeout occurred», и в error-логе nginx ошибки не будет: nginx ещё ждёт бэкенд, а Cloudflare уже закрыл соединение. Ищите в access-логе код 499 на ровно 100-й секунде — это и есть след 524. Код 504 через Cloudflare приходит только тогда, когда его отдал сам ваш сервер. Подробности — в справке Cloudflare: https://developers.cloudflare.com/support/troubleshooting/http-status-codes/cloudflare-5xx-errors/error-524/.

Обратите внимание на третью колонку: proxy_read_timeout в nginx считает не общее время запроса, а паузу между соседними порциями данных. Бэкенд, который выдаёт по строке каждые 30 секунд в течение 10 минут, 504 не получит. Это важно для стриминга и длинных экспортов — и объясняет, почему «таймаут 60 секунд» иногда срабатывает через три минуты. Описание директивы: https://nginx.org/ru/docs/http/ngx_http_proxy_module.html#proxy_read_timeout.

Что говорят логи

В error-логе nginx 504 выглядит однозначно:

2026/09/16 14:02:11 [error] 1187#1187: *48213 upstream timed out (110: Connection timed out)
while reading response header from upstream, client: 203.0.113.7, server: shop.example.ru,
request: "GET /catalog/?sort=price HTTP/2.0",
upstream: "fastcgi://unix:/run/php/php8.3-fpm.sock:", host: "shop.example.ru"

Здесь три полезных факта: время, конкретный URL и апстрим (сокет PHP-FPM). Если вместо while reading response header написано while connecting to upstream — бэкенд не принял даже соединение, и искать надо не медленный код, а очередь или сеть.

Чтобы видеть длительность каждого запроса, а не только упавших, добавьте в access-лог переменные апстрима:

log_format upstream_time '$remote_addr [$time_local] "$request" $status '
                         'rt=$request_time urt=$upstream_response_time '
                         'us=$upstream_status ua="$upstream_addr"';
access_log /var/log/nginx/access.log upstream_time;

После этого медленные запросы находятся одной командой — например, всё, что дольше 5 секунд, отсортированное по времени апстрима (при нескольких апстримах в urt будет список через запятую, +0 возьмёт первое значение):

awk '{ for (i = 1; i <= NF; i++) if ($i ~ /^urt=/) { v = substr($i, 5) + 0; if (v > 5) print v, $0 } }' \
  /var/log/nginx/access.log | sort -rn | head -20

Если urt близок к rt, время съедает бэкенд. Если rt заметно больше urt, а urt мал — проблема между nginx и клиентом (медленное соединение, большой ответ), и 504 тут ни при чём.

Для PHP-FPM включите slowlog — он пишет стек любого запроса, который выполняется дольше порога:

; /etc/php/8.3/fpm/pool.d/www.conf
request_slowlog_timeout = 5s
slowlog = /var/log/php8.3-fpm-www.slow.log
request_terminate_timeout = 55s

В slowlog вы увидите не «скрипт index.php работал 40 секунд», а именно функцию, на которой он завис: mysqli_query(), curl_exec(), file_get_contents(). Это обычно и есть ответ.

Про request_terminate_timeout одна оговорка: когда он срабатывает, PHP-FPM убивает воркер, соединение с nginx обрывается, и посетитель получает уже не 504, а 502 с записью upstream prematurely closed connection. Так что если после «починки» 504 у вас появились 502 — это тот же самый долгий запрос, просто его стали прерывать раньше.

Шесть типовых причин

Медленный SQL-запрос

Самая частая причина. Включите slow log базы и посмотрите, что в нём накопится за час:

-- MySQL / MariaDB
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;
SET GLOBAL log_queries_not_using_indexes = ON;
-- PostgreSQL, в postgresql.conf, затем reload
log_min_duration_statement = 1000

Прямо во время инцидента полезнее SHOW FULL PROCESSLIST (MySQL) или SELECT pid, now() - query_start AS age, state, query FROM pg_stat_activity WHERE state <> 'idle' ORDER BY age DESC; (PostgreSQL): они показывают, какие запросы висят прямо сейчас и кого они ждут — блокировку или диск. Найденный запрос прогоните через EXPLAIN: type: ALL и rows в сотни тысяч в MySQL или Seq Scan по большой таблице в PostgreSQL означают, что нужен индекс, а не таймаут.

Внешний API без таймаута внутри приложения

Приложение ходит в платёжный шлюз, службу доставки, SMS-провайдера или за курсом валют, и делает это без ограничения по времени. Пока чужой сервис думает, ваш воркер занят. В PHP default_socket_timeout равен 60 секундам, а у curl_exec() без CURLOPT_TIMEOUT ограничения нет вовсе — только ваш max_execution_time, который к сетевым операциям не применяется.

Правило: у каждого исходящего запроса должно быть два таймаута — на соединение и на ответ, и оба короче таймаута прокси перед вами:

curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
requests.get(url, timeout=(3, 10))  # (connect, read)
await fetch(url, { signal: AbortSignal.timeout(10_000) });

Найти такие вызовы помогает всё тот же slowlog PHP-FPM: curl_exec() или fsockopen() на вершине стека.

Тяжёлая работа в HTTP-запросе

Экспорт каталога в Excel, пересчёт цен, генерация PDF, ресайз десятка изображений при загрузке, отправка рассылки по нажатию кнопки — всё это выполняется в том же процессе, что и обычные страницы, и держит воркер минутами. Признак: 504 приходит на конкретный URL из админки, и всегда после того, как кто-то из сотрудников что-то запустил.

Лечится переносом в фоновую очередь: HTTP-обработчик ставит задачу (cron, systemd-timer, RQ, Celery, Laravel Queue) и сразу отвечает 202 или страницей «готовим файл, придёт на почту». Если задачу нельзя вынести быстро, дайте ей отдельный location со своим таймаутом — об этом ниже.

Не хватает воркеров

Когда все процессы PHP-FPM, gunicorn или uWSGI заняты, новые соединения копятся в backlog сокета. nginx их отправил и ждёт; когда очередь дойдёт до запроса, неизвестно. Пока очередь помещается в listen.backlog, это 504 по read-таймауту; когда backlog переполняется, nginx получает connect() to unix:… failed (11: Resource temporarily unavailable) и отдаёт 502 — та же нехватка воркеров, другой код. В логе PHP-FPM это выглядит так:

[16-Sep-2026 14:01:40] WARNING: [pool www] server reached pm.max_children setting (12), consider raising it

Прежде чем поднимать pm.max_children, посчитайте: pm.status_path = /fpm-status покажет listen queue, active processes и max children reached. Если очередь растёт при небольшой нагрузке — воркеры заняты чем-то долгим (см. три причины выше), и добавление процессов только отложит момент, когда закончится память.

Сеть между прокси и бэкендом

Актуально, когда nginx и приложение на разных машинах или в разных контейнерах. Файрвол, который молча отбрасывает пакеты (DROP), даёт 504 по proxy_connect_timeout; файрвол с REJECT дал бы мгновенную 502. Проверяется с хоста прокси напрямую:

curl -m 5 -o /dev/null -s -w '%{http_code} %{time_connect} %{time_starttransfer}\n' http://10.0.0.5:8080/health

Если здесь соединение висит, дальше смотрите iptables -L -n, маршруты и MTU, а не код приложения.

DNS-резолв внутри приложения

Приложение перед запросом к API или SMTP-серверу резолвит его имя. Если первый резолвер из /etc/resolv.conf недоступен, glibc ждёт timeout (по умолчанию 5 секунд) и делает attempts попыток (2) — уже 10 секунд на имя, если резолвер один. Каждый суффикс из search в resolv.conf умножает это ещё раз, а если приложение резолвит имя несколько раз за запрос, набегают те самые 20–30 секунд. Проверка:

time getent hosts api.partner.example
dig +time=2 +tries=1 api.partner.example @$(awk '/^nameserver/{print $2; exit}' /etc/resolv.conf)

Лечится починкой резолвера или локальным кэширующим DNS (systemd-resolved, unbound) и options timeout:2 attempts:1 в resolv.conf.

Замерить время по этапам

Прежде чем лезть в конфиги, разложите один запрос на составляющие. curl умеет это сам:

curl -o /dev/null -s -w 'dns=%{time_namelookup} connect=%{time_connect} tls=%{time_appconnect} ttfb=%{time_starttransfer} total=%{time_total} code=%{http_code}\n' \
  https://shop.example.ru/catalog/?sort=price

Типичный вывод при 504:

dns=0.012 connect=0.031 tls=0.078 ttfb=60.081 total=60.082 code=504

DNS, соединение и TLS заняли доли секунды, а первый байт пришёл ровно через 60 секунд — это nginx сдался по fastcgi_read_timeout. Если бы ttfb был 100,0 и код 524 — сдался Cloudflare. Повторите тот же запрос в обход прокси, прямо на порт приложения: если и там 60+ секунд, проблема в бэкенде; если приложение отвечает за две секунды — между ними.

Снаружи то же самое даёт проверка доступности сайта с нескольких точек: она показывает код ответа и время по тем же этапам — DNS, соединение, TLS, первый байт, — а просмотр заголовков подскажет, чей это ответ — по Server, cf-ray и другим заголовкам сервера.

Когда таймаут всё-таки нужно поднять

Поднимать оправдано, если запрос по своей природе долгий и вы это знаете: экспорт отчёта, импорт прайса, вебхук, который синхронно ждёт обработки. Тогда таймаут поднимается точечно, для одного location, а не для всего сервера:

location /admin/export/ {
    fastcgi_read_timeout 300s;
    fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    include fastcgi_params;
}

fastcgi_read_timeout — не единственный порог: request_terminate_timeout в PHP-FPM задаётся на весь пул, а не на location, и при 60s убьёт экспорт на первой минуте — nginx отдаст 502. Для долгих адресов заведите отдельный пул с request_terminate_timeout = 300s и направьте на его сокет только этот location, либо поднимайте порог осознанно для всего пула.

И согласуйте всю цепочку: каждый следующий слой должен ждать дольше предыдущего, иначе первым сдастся не тот.

СлойПараметрПример значения
Запрос к базеmax_execution_time (MySQL, мс, только SELECT) / max_statement_time (MariaDB, с) / statement_timeout (PostgreSQL, мс)20 000 мс / 20 с
Исходящий HTTP из приложенияCURLOPT_TIMEOUT, timeout=10 s
Скриптmax_execution_time (PHP; считает только CPU-время, ожидание базы и сети не ограничивает)30 s
PHP-FPMrequest_terminate_timeout55 s
nginxfastcgi_read_timeout60 s (для экспорта — 300 s)
CDNCloudflare100 s, не изменить

Из таблицы видно, что за Cloudflare любой fastcgi_read_timeout больше 100 секунд бессмыслен: посетитель получит 524 раньше. Для действительно долгих операций там остаётся только фоновая задача с опросом статуса.

Как ловить 504 мониторингом

504 редко приходит внезапно. Обычно неделю до этого TTFB медленно растёт с 300 мс до 3 секунд, потом до 20, и лишь затем упирается в таймаут. Поэтому мониторить нужно не только «сайт открылся», но и время ответа как метрику, с порогом заметно ниже таймаута прокси: например, 5 секунд при fastcgi_read_timeout 60s.

Два практических момента. Первый: таймаут самой проверки. Если монитор ждёт 15 секунд, а nginx — 60, вы увидите в инциденте «таймаут», а не «HTTP 504»; для реакции это одно и то же, но помните об этом, читая причину. Второй: проверяйте не только главную. Главная часто закэширована и отвечает мгновенно, а 504 живёт в каталоге с фильтрами, поиске или корзине — заведите отдельную проверку на тяжёлую страницу.

В мониторинге доступности сайта для этого есть порог максимального времени ответа и график времени отклика по проверкам: рост TTFB виден до того, как он превратится в 504, а уведомление приходит на рост, а не на падение.

Коротко

  • 504 — прокси не дождался бэкенда: nginx (proxy_read_timeout, fastcgi_read_timeout, 60 с), Apache (ProxyTimeout), Cloudflare (524 через 100 с).
  • В error-логе nginx ищите upstream timed out, в access-лог добавьте $upstream_response_time, в PHP-FPM включите request_slowlog_timeout.
  • Шесть причин: медленный SQL, внешний API без таймаута, тяжёлая работа в HTTP-запросе, нехватка воркеров, сеть между прокси и бэкендом, DNS внутри приложения.
  • Разложите запрос через curl -w: TTFB ровно 60 с — сработал nginx, 100 с — Cloudflare; повторите в обход прокси.
  • Таймаут поднимайте точечно, для конкретного location, и согласуйте цепочку база → приложение → PHP-FPM → nginx → CDN.
  • Мониторьте время ответа тяжёлых страниц с порогом ниже таймаута прокси — 504 видна за дни до появления.

Вопросы и ответы

Чем ошибка 504 отличается от 502?
502 — прокси получил от бэкенда неправильный ответ или соединение оборвалось: процесс не запущен, упал или был убит. 504 — бэкенд принял запрос, но не ответил за отведённое время. При 504 приложение обычно живо и занято чем-то долгим.
Почему 504 появляется только у некоторых пользователей?
Потому что она привязана к конкретным запросам, а не к серверу целиком: тяжёлая страница каталога с фильтрами, поиск, личный кабинет с большой историей заказов. Главная при этом отдаётся из кэша и работает. Найдите URL в error-логе nginx и проверьте именно его.
Сколько секунд по умолчанию ждёт nginx до 504?
60 секунд: столько стоит в proxy_read_timeout, fastcgi_read_timeout и uwsgi_read_timeout. Причём считается пауза между порциями ответа, а не общее время, поэтому запрос, который что-то отдаёт понемногу, может длиться дольше.
Ошибка 504 — это проблема на моей стороне или у хостинга?
Почти всегда на стороне сайта: медленный запрос к базе, внешний API без таймаута, нехватка воркеров. Хостинг виноват, если 504 отдаёт его балансировщик перед вашим сервером, а сервер при прямом обращении отвечает быстро — это проверяется curl в обход прокси.
Можно ли просто увеличить таймаут, чтобы убрать 504?
Для одного заведомо долгого адреса — да, отдельным location. Для всего сайта — нет: долгие запросы продолжат занимать воркеры, очередь вырастет, и 504 вернётся, а за Cloudflare любой таймаут больше 100 секунд бесполезен из-за кода 524.
Влияет ли ошибка 504 на позиции сайта в поиске?
Кратковременная — почти нет, робот повторит обход. Если 504 держится днями, поисковик перестаёт обновлять страницы и может исключить их из индекса, поэтому важно узнать о проблеме в первые минуты, а не из отчёта вебмастера.

Не проверяйте вручную — следите автоматически

Нотифарио проверит сайт с нескольких точек и пришлёт уведомление в Telegram, MAX, по SMS или звонком, когда что-то сломается. Оплата только за проверки, 50 ₽ на старт.

Подключить бесплатно Проверить сайт сейчас