Ошибка 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_timeout | 60 s | Пауза между двумя чтениями ответа, а не общее время |
| nginx → PHP-FPM | fastcgi_read_timeout | 60 s | То же для FastCGI |
| nginx → uWSGI | uwsgi_read_timeout | 60 s | То же для uWSGI |
| nginx, установление соединения | proxy_connect_timeout | 60 s (не больше 75 s) | Только TCP-handshake; при его истечении тоже 504 |
| Apache mod_proxy | ProxyTimeout или timeout= в ProxyPass | значение Timeout, 60 s | Ожидание ответа бэкенда |
| Apache mod_fcgid | FcgidIOTimeout | 40 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-FPM | request_terminate_timeout | 55 s |
| nginx | fastcgi_read_timeout | 60 s (для экспорта — 300 s) |
| CDN | Cloudflare | 100 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?
Почему 504 появляется только у некоторых пользователей?
Сколько секунд по умолчанию ждёт nginx до 504?
Ошибка 504 — это проблема на моей стороне или у хостинга?
Можно ли просто увеличить таймаут, чтобы убрать 504?
Влияет ли ошибка 504 на позиции сайта в поиске?
Не проверяйте вручную — следите автоматически
Нотифарио проверит сайт с нескольких точек и пришлёт уведомление в Telegram, MAX, по SMS или звонком, когда что-то сломается. Оплата только за проверки, 50 ₽ на старт.
Подключить бесплатно Проверить сайт сейчас