UptimeChecker

Ошибка 504 Gateway Timeout: причины и решение

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

Что такое ошибка 504

Код ответа 504 Gateway Timeout появляется, когда шлюз (обычно Nginx, Apache или балансировщик) не дождался ответа от сервера, который стоит за ним, за отведённое время. Запрос был принят и передан дальше, но тот, кто должен был его обработать, молчал дольше таймаута — и шлюз оборвал ожидание, вернув клиенту 504.

Важно понимать архитектуру: почти никогда сайт в интернете не отдаёт страницы «сам». Типичная цепочка выглядит так:

посетитель → Nginx (шлюз) → PHP-FPM / Node.js / Gunicorn → база данных / внешний API

504 возникает именно на границе «шлюз → следующий за ним сервис». Сам шлюз жив и здоров, он просто устал ждать. Это отличает 504 от 500 (когда падает само приложение) и от 502 (когда шлюз вообще не может установить соединение с бэкендом).

Цепочка запроса при 504 Gateway Timeout

Ключевое слово в названии ошибки — timeout, «таймаут». Где-то в цепочке запрос занял больше времени, чем ему было позволено. Поэтому диагностика 504 — это всегда поиск узкого места: что именно отвечало слишком долго.

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

Обе ошибки возникают на границе «шлюз → бэкенд», и их часто путают. Разница в том, что именно пошло не так:

Ошибка Что произошло Что это значит на практике
502 Bad Gateway Шлюз получил некорректный ответ или вообще не смог подключиться к бэкенду Бэкенд лежит, порт/сокет недоступен, бэкенд упал с ошибкой
504 Gateway Timeout Шлюз подключился, передал запрос, но не дождался ответа за отведённое время Бэкенд жив, но завис на задаче: медленный запрос, зависшая БД, перегрузка

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

Основные причины 504

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

Самая частая причина. Приложение отправляет SQL-запрос, который выполняется десятки секунд из-за отсутствующего индекса, большой таблицы или блокировки. Всё это время PHP-FPM (или другой воркер) занят, а шлюз ждёт ответа — и в итоге не дожидается.

2. Зависший внешний API

Приложение обращается к стороннему сервису — платёжной системе, почтовому API, CRM. Если этот сервис тормозит или лежит, а в коде не настроен таймаут, ваш сайт будет ждать ответа вместе с ним.

3. Недостаточно воркеров

Даже быстрые запросы упираются в 504, если воркеров мало, а трафик большой: каждый запрос встаёт в очередь, и очередь растёт быстрее, чем обрабатывается.

4. Слишком маленький таймаут шлюза

Иногда сам запрос легитимен и долгий (генерация большого отчёта, импорт, экспорт), а таймаут шлюза настроен на «средний» запрос. Тогда 504 — это вопрос конфигурации, а не производительности.

5. Блокировки и взаимные ожидания

Два запроса держат ресурсы и ждут друг друга, образуя дедлок. Или один длинный запрос держит блокировку таблицы, и все остальные запросы к ней встают в очередь.

Диагностика: находим виновника

504 — это всегда вопрос «где именно застряло». Разберём пошагово.

Шаг 1. Подтверждаем 504 и замеряем время

curl -s -o /dev/null -w 'code=%{http_code} total=%{time_total}s\n' https://example.com/slow-page

Вывод:

code=504 total=60.00s

Запрос занял ровно 60 секунд и завершился 504. Ровное, «круглое» время — важный маркер: это не случайное зависание, а сработавший таймаут. Запомните это число (60 секунд) — оно должно совпасть с настройкой таймаута в конфиге шлюза, и мы к нему вернёмся.

Шаг 2. Смотрим, что говорит шлюз

Nginx пишет причину в свой журнал ошибок:

tail -f /var/log/nginx/error.log

Типичная запись:

upstream timed out (110: Connection timed out) while reading response header from upstream

Разберём: upstream timed out — таймаут именно на апстриме (бэкенде), while reading response header — Nginx отправил запрос, но так и не получил даже заголовки ответа. Это значит, что проблема не в передаче данных, а в том, что бэкенд вообще не начал отвечать за отведённое время.

Если вместо этого видите запись с connect() — это уже 502, а не 504: шлюз не смог даже подключиться.

Шаг 3. Проверяем бэкенд напрямую, в обход шлюза

Ключевой приём: обратитесь к бэкенду напрямую, минуя Nginx. Если бэкенд слушает порт 9000 (PHP-FPM) или 3000 (Node.js):

curl -s -o /dev/null -w 'code=%{http_code} total=%{time_total}s\n' http://127.0.0.1:3000/

Если прямой запрос к бэкенду тоже висит долго — проблема внутри приложения (запрос к БД, внешний API, код). Если напрямую всё быстро, а через шлюз 504 — смотрите конфиг шлюза и его таймауты.

Шаг 3.5. Раскладываем время ответа по фазам

Когда неясно, на каком этапе застревает запрос, curl умеет показать время по каждой фазе соединения отдельно. Это помогает отсечь сеть и TLS от собственно обработки:

curl -s -o /dev/null -w \
  'dns=%{time_namelookup}s tcp=%{time_connect}s tls=%{time_appconnect}s first_byte=%{time_starttransfer}s total=%{time_total}s\n' \
  https://example.com/slow-page

Возможный вывод:

dns=0.03s tcp=0.05s tls=0.12s first_byte=58.90s total=58.95s

Разберём построчно:

  • dns=0.03s — резолвинг домена занял 30 миллисекунд. DNS не виноват;
  • tcp=0.05s — соединение установилось за 50 мс. Сеть в порядке;
  • tls=0.12s — рукопожатие TLS заняло 120 мс. Не проблема;
  • first_byte=58.90s — а вот первый байт ответа пришёл только через 58,9 секунды. Всё это время сервер «думал»: запрос был принят, но обработка заняла почти минуту;
  • total=58.95s — суммарное время совпадает с first_byte, значит задержка целиком лежит в обработке на сервере, а не в передаче данных.

Если бы у вас было большое tls или tcp, стоило бы копать сеть и сертификаты. Здесь картина однозначная: сервер долго считает — идите в базу и код приложения.

Шаг 4. Ищем медленные запросы в базе

Если подозрение падает на базу, включите лог медленных запросов и посмотрите, что выполняется дольше всего. В MySQL/PostgreSQL это даёт список запросов с временем выполнения — обычно наверху оказывается один и тот же запрос без индекса.

Таблица: симптом → причина → решение

Симптом Вероятная причина Что делать
504 на конкретных страницах (отчёты, поиск, экспорт) Медленный SQL-запрос или тяжёлая операция Найти запрос в slow-логе, добавить индекс, переписать запрос
504 ровно через N секунд на любой странице Таймаут шлюза меньше времени обработки Увеличить proxy_read_timeout / fastcgi_read_timeout
504 в пиковые часы, ночью ок Не хватает воркеров бэкенда Увеличить лимит процессов PHP-FPM / воркеров приложения
504 на страницах, работающих с внешним сервисом Завис внешний API Настроить таймаут и retry в коде, вынести вызов в фон
504 внезапно на всём сайте Бэкенд завис (дедлок, OOM) Проверить логи приложения, перезапустить бэкенд
504 + запись upstream timed out в логе Nginx Бэкенд не ответил за отведённое время Идти по шагам диагностики выше
504 только у одного пользователя/скрипта Долгая операция конкретного запроса Профилировать именно этот запрос

Исправляем в Nginx: таймауты

Если вы подтвердили, что запрос объективно долгий (генерация отчёта, импорт данных), а не зависший, — нужно дать шлюзу больше времени. В Nginx за ожидание ответа от бэкенда отвечают три директивы, и их часто путают:

location / {
    proxy_pass http://backend;
    proxy_connect_timeout 10s;   # время на установку соединения с бэкендом
    proxy_read_timeout   60s;    # время ожидания ответа после отправки запроса
    proxy_send_timeout   60s;    # время на отправку запроса бэкенду
}

Для 504 важен именно proxy_read_timeout — это то, сколько шлюз ждёт ответа после того, как отправил запрос. Если ваш отчёт генерируется 90 секунд, а proxy_read_timeout равен 60 — вы гарантированно получите 504.

Для связки Nginx + PHP-FPM вместо proxy_* используются fastcgi_*-директивы:

location ~ \.php$ {
    fastcgi_pass unix:/run/php/php8.2-fpm.sock;
    fastcgi_read_timeout 120s;
}

Здесь fastcgi_read_timeout 120s — то же самое по смыслу: сколько Nginx ждёт ответа от PHP-FPM. Поднимайте таймаут до значения, заведомо большего, чем ваша самая долгая легитимная операция.

После изменения конфига проверьте его и перезагрузите Nginx:

nginx -t && systemctl reload nginx

nginx -t проверяет синтаксис (если есть ошибка — Nginx не перезагрузится и продолжит работать на старом конфиге), systemctl reload nginx применяет изменения без обрыва соединений.

Но помните: поднимать таймаут — это лечить симптом. Если запрос действительно должен выполняться секунды, а не минуты, сначала разберитесь, почему он медленный.

Исправляем в PHP-FPM: воркеры

Если 504 появляется в пиковые часы, причина скорее в том, что запросы стоят в очереди, а не в таймауте. Проверьте логи PHP-FPM:

tail -f /var/log/php8.2-fpm.log

Запись:

WARNING: [pool www] server reached pm.max_children setting (20), consider raising it

Означает: все 20 процессов заняты, новые запросы ждут. Пока ждут — шлюз тикает таймаутом, и вот вам 504. Решение — поднять лимит в /etc/php/8.2/fpm/pool.d/www.conf, но с оглядкой на память:

pm.max_children = 50

Формула та же, что и при перегрузке: посчитайте свободную память и размер одного PHP-процесса, чтобы не уйти в своп (своп сделает всё ещё медленнее и превратит 504 в бесконечные).

Исправляем в приложении: медленные вызовы

Таймауты и воркеры — это лечение на уровне инфраструктуры. Но чаще всего настоящий виновник — один медленный запрос. Три типовых случая:

  1. Отсутствующий индекс в БД. Запрос, который должен занимать миллисекунды, сканирует всю таблицу и занимает секунды. Добавление индекса решает проблему радикальнее любого таймаута;
  2. Синхронный вызов внешнего API. Сайт ждёт ответа стороннего сервиса, который может тормозить. Вынесите такой вызов в фоновую очередь, а пользователю отвечайте сразу;
  3. Тяжёлые операции в обработчике. Генерация PDF, ресайз изображений, массовая рассылка — всё это не должно выполняться в момент обработки HTTP-запроса. Переносите в очередь задач (queue/jobs).

Для каждого из этих случаев 504 — это не поломка, а индикатор того, что операция выполняется не там и не тогда.

Профилактика: ловить до того, как заметит клиент

504 почти всегда развивается постепенно: сначала один запрос становится чуть медленнее, потом — заметно медленнее, и в какой-то момент пересекает порог таймаута. Значит, проблему можно поймать на подлёте, если следить за временем ответа.

  • Следите за временем отклика. Если среднее время ответа растёт от недели к неделе — вы движетесь к 504. Порог таймаута известен, и приближение к нему видно заранее;
  • Проверяйте доступность снаружи. 504 — это то, что видит посетитель, а не то, что видно изнутри сервера. Проверить, как сайт отвечает прямо сейчас, можно через проверку доступности: внешняя проверка фиксирует и код ответа, и время отклика;
  • Настройте алерты. Если 504 появился — вы должны узнать об этом в первые минуты, а не из письма клиента. Email-оповещение о недоступности решает эту задачу.

Внутренний мониторинг вроде Zabbix полезен для ресурсов сервера — CPU, памяти, диска. Но он смотрит на сервер изнутри и не покажет, что именно сайт отдаёт наружу: для этого нужна внешняя проверка доступности, которая запрашивает страницу так, как это делает реальный пользователь. Эти два подхода дополняют друг друга.

Коротко

Ошибка 504 — это «шлюз не дождался ответа от бэкенда». В отличие от 502, бэкенд при 504 обычно жив — просто запрос выполняется дольше, чем позволено. Начинайте диагностику с замера времени (curl -w) и лога шлюза: если время ответа ровное и совпадает с таймаутом — вы нашли порог, который нужно либо поднять, либо устранить причину медленности (индекс в БД, зависший API, нехватка воркеров).

Чтобы 504 не заставал врасплох, следите за временем ответа и доступностью сайта снаружи — начните с бесплатной проверки доступности. А если у вас ошибка 502 вместо 504, разбор причин и решений — в статье ошибка 502 Bad Gateway.