Поддержка и абонентское обслуживание

Ошибка 504 Gateway Timeout: почему сервер не дождался ответа

Чем 504 отличается от 502, какие операции чаще всего в неё упираются и что делать с импортом, выгрузками и внешними интеграциями.

12 минут чтения 2 Дмитрий Акиндинов
Содержание
  1. Что означает 504 и чем он отличается от 502 и 503
  2. Где именно стоит таймаут
  3. Пять мест, где 504 ловят чаще всего
  4. Диагностика: порядок действий
  5. Быстрые меры: что сделать сегодня
  6. Долгие меры: очередь, консоль, запрос
  7. Что писать хостингу и какие цифры приложить
  8. Когда 504 это уже не про таймаут
  9. Короткий чек-лист
  10. Частые вопросы

504 приходит тогда, когда операция не уложилась в отведённое время. Ловят её на импорте прайса, на обмене с 1С и на формировании отчётов. Увеличение таймаута лечит симптом, а причина обычно в одном запросе, который тащит десятки тысяч строк без ограничения.

Ошибка 504 gateway timeout приходит в самый неудобный момент: на середине импорта прайса, во время выгрузки заказов в 1С, при формировании отчёта за квартал. Страница висит ровно минуту, потом браузер показывает голое «Gateway Timeout», и совершенно непонятно, что успело записаться в базу, а что нет. Хостинг отвечает «у нас всё работает», плагин молчит, в админке никаких следов.

Статья для владельцев сайтов и админов, которые ловят 504 регулярно. Разберу, где именно в цепочке стоит таймаут, как за двадцать минут найти виновника, какие меры дают результат, а какие только отодвигают проблему на месяц.

Шлюз с остановившимися часами и очередь запросов

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

Первое, что нужно усвоить: 504 отдаёт не ваш код. Его отдаёт прокси, который стоит перед PHP. На типовом хостинге цепочка выглядит так: браузер обращается к nginx, nginx передаёт запрос в php-fpm через сокет, php-fpm запускает скрипт, скрипт лезет в MySQL и по дороге дёргает пару внешних API. Nginx ждёт ответа от php-fpm ограниченное время. Не дождался, разорвал соединение, показал 504.

То есть 504 это не сообщение об ошибке в вашем сайте. Это сообщение о том, что терпение кончилось у посредника.

КодЧто произошлоКто отдал ответЧастая причина
502 Bad Gatewayбэкенд оборвал соединение или отдал мусорnginx, LiteSpeedphp-fpm упал по памяти, сегфолт, перезапуск пула
503 Service Unavailableбэкенд занят или выключенnginx или сам PHPвсе воркеры заняты, режим обслуживания, лимит хостинга
504 Gateway Timeoutбэкенд не ответил за отведённое времяпрокси перед бэкендомдолгий скрипт, зависший запрос к базе, молчащий внешний API

Разница между 502 и 504 практическая, а не академическая. 502 обычно означает, что процесс умер: смотреть надо память, лимиты пула и логи ядра. 504 означает, что процесс жив и до сих пор что-то делает: смотреть надо время выполнения.

Из этого следует неприятное свойство, о котором мало кто думает. Когда браузер получил 504, ваш скрипт чаще всего продолжает работать. Nginx закрыл соединение со своей стороны, но php-fpm об этом узнает только когда попытается что-то отдать в вывод. Долгий импорт, который молча пишет в базу и ничего не печатает, спокойно доработает до конца или до собственного лимита.

Отсюда классика жанра: владелец видит 504, нажимает F5, запускает импорт второй раз, и в каталоге появляются дубли товаров. Или дважды уходят заказы в CRM. Прежде чем повторять операцию после 504, надо проверить, что она реально не доехала.

Где именно стоит таймаут

Таймаутов в цепочке не один, их пять-шесть, и срабатывает всегда самый короткий. Пока не знаешь, какой именно, любые попытки «увеличить лимит» превращаются в гадание.

СлойПараметрТипичное значениеГде лежит
nginx как проксиproxy_read_timeout60sконфиг сайта
nginx как fastcgifastcgi_read_timeout60sконфиг сайта
Apache с mod_proxy_fcgiProxyTimeout, Timeout60-300sконфиг vhost
LiteSpeedConnection Timeout, lsapi60-300sпанель хостинга
php-fpmrequest_terminate_timeout0 или 120sконфиг пула
PHPmax_execution_time30sphp.ini, .user.ini
PHPmax_input_time60sphp.ini
MySQLwait_timeout, max_execution_time28800s, 0конфиг сервера БД
HTTP-клиент в кодеCURLOPT_TIMEOUT, аргумент timeout5-30sкод плагина

Теперь тонкость, из-за которой люди неделями чинят не то. На Linux max_execution_time считает только процессорное время самого скрипта. Ожидание ответа от MySQL, чтение файла, вызов чужого API через curl в этот счётчик не попадают. Скрипт может пять минут висеть на одном тяжёлом SELECT и ни на секунду не приблизиться к своему тридцатисекундному лимиту.

Именно поэтому 504 так любит тяжёлые запросы и внешние интеграции. Если бы упирались в max_execution_time, вы бы увидели белый экран с «Maximum execution time exceeded» и запись в error_log. А вы видите 504, значит PHP спокойно ждёт кого-то ещё, а nginx ждать перестал.

Проверить свои значения проще всего так:

# что реально видит PHP на этом сайте
php -i | grep -E "max_execution_time|max_input_time|memory_limit"

# что стоит в nginx для домена
grep -rE "fastcgi_read_timeout|proxy_read_timeout" /etc/nginx/

# что стоит в пуле php-fpm
grep -E "request_terminate_timeout|pm.max_children" /etc/php-fpm.d/*.conf

На shared-хостинге доступа к первым двум командам не будет. Тогда значения PHP смотрите через временный phpinfo.php в корне (и удалите его сразу после), а настройки nginx спрашивайте у поддержки, они обычно отвечают честно.

Пять мест, где 504 ловят чаще всего

За шесть лет абонентки набралась короткая и очень предсказуемая коллекция.

Импорт товаров

WP All Import, штатный импортёр WooCommerce, загрузка CSV в OpenCart, обработка прайса в Битриксе. Пока в файле 200 позиций, всё летает. На 3000 позициях с картинками, вариациями и пересчётом атрибутов один шаг импорта перестаёт укладываться в минуту.

Отдельная беда, когда импортёр качает изображения по ссылкам из прайса. Каждая картинка это отдельный HTTP-запрос к чужому серверу. Двадцать медленных картинок по три секунды дают минуту простоя на пустом месте.

Обмен с 1С

В Битриксе это /bitrix/admin/1c_exchange.php, и он ходит пошагово. Если в настройках обмена стоит слишком большой шаг (количество элементов за один запрос), сервер не успевает его переварить. Классический симптом: обмен доходит до 40 процентов и встаёт, в логе 1С висит «Ошибка обмена данными», в логе nginx ровно в это время таймаут.

Отчёты и выгрузки

«Выгрузить все заказы за 2025 год в Excel». Плагин делает один запрос без лимита, тащит 40 тысяч строк с join по мета-таблицам, потом строит из них файл в памяти. Тут обычно ловится сразу два лимита: сначала время, потом память.

Крон, повешенный на wp-cron

WP-Cron запускается при заходе посетителя на сайт. Если в очереди накопилась тяжёлая задача (рассылка, пересчёт остатков, синхронизация), она поедет прямо в запросе живого пользователя. Пользователь получает 504, задача остаётся в очереди, следующий посетитель получает то же самое. Сайт начинает выдавать таймауты случайным людям на случайных страницах.

Внешние API прямо в шаблоне

Виджет курса валют, расчёт доставки СДЭК, подтягивание отзывов, проверка остатков в чужой системе. Пока сервис отвечает, всё хорошо. Как только он лёг или начал отвечать за 40 секунд, ваш сайт превращается в его заложника. Хуже всего, когда такой вызов сидит в шапке или футере: тогда 504 отдаёт весь сайт, включая главную.

Диагностика: порядок действий

Не надо сразу лезть в конфиги. Сначала цифры.

Шаг 1. Замерить время и убедиться, что это ровно таймаут.

curl -o /dev/null -s 
  -w "code=%{http_code} ttfb=%{time_starttransfer} total=%{time_total}n" 
  "https://example.ru/wp-admin/admin-ajax.php?action=heavy_import_step"

Если total стабильно совпадает с круглым числом (60.02, 120.04, 300.01), вы нашли таймаут. Круглые секунды это всегда конфиг, а не случайность. Если время каждый раз разное, это не таймаут, а плавающая нагрузка, и разбираться надо с медленным TTFB на shared-хостинге.

Шаг 2. Найти строку в логе nginx.

2026/09/28 14:12:07 [error] 1841#1841: *93217 upstream timed out (110: Connection timed out)
while reading response header from upstream, client: 5.18.x.x, server: example.ru,
request: "POST /wp-admin/admin-ajax.php HTTP/2.0",
upstream: "fastcgi://unix:/var/run/php-fpm/example.sock:", host: "example.ru"

Читается она буквально. while reading response header означает, что бэкенд за отведённое время не прислал вообще ничего, даже заголовков. Если бы там было while reading upstream, значит ответ начался и оборвался на середине, а это уже другой разговор (обычно долгая отдача большого файла).

Шаг 3. Включить slowlog php-fpm. Это самый недооценённый инструмент во всей истории. Он показывает стек вызовов в момент зависания, то есть буквально называет функцию и файл.

; конфиг пула php-fpm
request_slowlog_timeout = 5s
slowlog = /var/log/php-fpm/example-slow.log

В логе вы увидите примерно такое: wp_remote_post() /wp-content/plugins/some-crm/api.php:118. После этого искать больше нечего.

Шаг 4. Посмотреть, что делает база прямо в момент зависания.

SHOW FULL PROCESSLIST;

Столбец Time показывает, сколько секунд живёт запрос. Всё, что висит дольше 5 секунд, кандидат в виновники. Заодно стоит включить лог медленных запросов: long_query_time = 1 даст на типовом сайте небольшой файл, который читается за пять минут.

Шаг 5. Если это WordPress, поставить Query Monitor. Он показывает время каждого запроса, время каждого HTTP-вызова наружу и раскладку по плагинам. На админских страницах работает так же, как на фронте.

На shared-хостинге шагов 2 и 3 не будет: логов nginx вам никто не даст. Останутся error_log в корне сайта, панель хостинга с графиками CPU и Query Monitor. Этого обычно хватает, чтобы понять направление.

Быстрые меры: что сделать сегодня

Быстрые меры делятся на честные и на обезболивающие. Разница в том, чинят они причину или прячут её.

Поднять таймаут. Обезболивающее, но иногда единственный вариант на сегодня. Если операция объективно занимает 90 секунд и делать её чаще раза в неделю никто не собирается, поднять лимит до 300 секунд нормально.

location ~ .php$ {
    fastcgi_pass unix:/var/run/php-fpm/example.sock;
    fastcgi_read_timeout 300s;
    fastcgi_send_timeout 300s;
    include fastcgi_params;
}

На shared-хостинге конфиг nginx вы не тронете, но PHP-часть обычно можно поправить через .user.ini в корне сайта:

; .user.ini, работает при PHP в режиме FastCGI/FPM
max_execution_time = 300
memory_limit = 512M

Учтите, что .user.ini перечитывается не мгновенно: по умолчанию user_ini.cache_ttl равен 300 секундам. Поправили, не увидели изменений в phpinfo, подождите пять минут, прежде чем считать, что не сработало. Правки через php_value в .htaccess на большинстве современных хостингов не работают вовсе, а на LiteSpeed и в CGI-режиме роняют сайт в 500.

Разбить операцию на части. Честная мера и почти всегда самая эффективная. В импортёрах есть настройка размера шага, её просто надо найти:

  • WP All Import: «Records per iteration» в разделе Settings, ставим 20-50 вместо дефолтных 100+;
  • WooCommerce Product CSV Importer: параметр woocommerce_product_csv_importer_batch_size;
  • Битрикс, обмен с 1С: количество элементов в одном шаге, разумный старт 20;
  • OpenCart: импорт файлами по 500 строк вместо одного файла на 5000.

Каждый шаг укладывается в 10-20 секунд, таймаут перестаёт быть проблемой в принципе.

Вынести wp-cron из веб-запроса. Двухминутная работа, которая убирает целый класс случайных 504.

// wp-config.php, выше строки "That's all, stop editing"
define('DISABLE_WP_CRON', true);
# крон хостинга, раз в 5 минут
*/5 * * * * cd /home/user/example.ru && /usr/bin/php8.2 /usr/local/bin/wp cron event run --due-now --quiet

Теперь тяжёлые задачи выполняются в консоли, где веб-таймаутов нет, а посетители не оплачивают своим ожиданием вашу рассылку.

Долгие меры: очередь, консоль, запрос

Если 504 повторяется, увеличением лимитов дело не кончится. Придётся менять способ выполнения работы.

Очередь задач

Идея простая: веб-запрос только кладёт задание в список и сразу отдаёт страницу, а выполняет его фоновый процесс маленькими порциями. В WooCommerce библиотека для этого уже стоит, называется Action Scheduler, задания лежат в таблице wp_actionscheduler_actions. Своё задание ставится в одну строку:

as_enqueue_async_action('sf_import_chunk', ['offset' => 0, 'limit' => 50]);

Для не-WordPress проектов та же логика собирается за пару часов: таблица jobs со статусами, крон раз в минуту, обработка по 50 записей за проход. Это не архитектурный подвиг, а обычная рабочая практика.

Консольный запуск

Разовая тяжёлая операция вообще не должна ходить через браузер. По SSH ограничений на время нет:

wp eval-file scripts/recount-stock.php --allow-root
php -d max_execution_time=0 -d memory_limit=1G bin/import.php prices.csv

Пересчёт остатков на 12 тысяч товаров, который в админке отваливался по 504 через минуту, в консоли отрабатывает за четыре минуты и без единой ошибки.

Оптимизация запроса

Иногда причина не в объёме работы, а в одном плохом SELECT. Смотрим план:

EXPLAIN SELECT p.ID, p.post_title FROM wp_posts p
JOIN wp_postmeta m ON m.post_id = p.ID
WHERE p.post_type = 'product' AND m.meta_key = '_stock_status';

type: ALL и десятки тысяч строк в столбце rows означают полный перебор таблицы. Лечится индексом или переписыванием запроса. Отдельно проверьте размер автозагрузки опций: раздутый autoload добавляет задержку вообще ко всем запросам сайта, и на этом фоне любая тяжёлая операция начинает не укладываться в лимит. Как это разбирается, писал в статье про раздутую базу WordPress и autoload.

Внешние вызовы под контролем

Любой синхронный запрос к чужому API в шаблоне страницы это мина. Правил ровно два: короткий таймаут и кэш ответа.

$response = wp_remote_post($url, [
    'timeout'  => 8,   // по умолчанию 5, но плагины любят ставить 30 и больше
    'blocking' => true,
]);

if (is_wp_error($response)) {
    // отдаём страницу с закэшированным значением, а задачу ставим в очередь
    return get_transient('sf_last_good_rates');
}

Восемь секунд вместо тридцати означают, что упавший сервис доставки замедлит вашу страницу, но не уронит её в таймаут.

Что писать хостингу и какие цифры приложить

Письмо «у нас 504, почините» приносит ответ «проверили, всё работает» примерно всегда. Письмо с фактурой обрабатывают за час, потому что администратору не надо ничего домысливать.

Что должно быть в обращении:

  • точный URL, на котором воспроизводится;
  • дата, время с точностью до минуты и часовой пояс;
  • IP, с которого вы обращались;
  • результат замера curl с time_total и кодом ответа;
  • строка из лога, если она у вас есть;
  • текущие значения max_execution_time и memory_limit из phpinfo;
  • конкретная просьба, а не описание чувств;
  • вопрос про лимиты аккаунта в это время.

Готовый шаблон, который можно скопировать и подставить свои данные:

Домен: example.ru, аккаунт cr43080.
Проблема: POST /wp-admin/admin-ajax.php стабильно отдаёт 504 ровно через 60 секунд.
Время: 28.09.2026, 14:10-14:20 MSK. IP клиента: 5.18.x.x.
Замер: curl -w "%{time_total}" показывает 60.02 и code=504, воспроизводится 5 раз из 5.
PHP на сайте: max_execution_time=300, memory_limit=512M (проверено через phpinfo).
Просьба: поднять fastcgi_read_timeout и proxy_read_timeout до 300s для домена example.ru.
Вопросы: 1) какое значение стоит сейчас; 2) были ли в указанный интервал
срабатывания лимитов CPU, памяти или числа процессов по аккаунту.

Последний вопрос важнее, чем кажется. Часто таймаут не причина, а следствие: аккаунт упёрся в лимит CPU, процессы встали в очередь, всё замедлилось втрое. Как это выглядит со стороны и как понять, что вы упираетесь именно в потолок тарифа, разобрано в материале про лимиты shared-хостинга по CPU и памяти.

Когда 504 это уже не про таймаут

Есть момент, когда пора признать: дело не в конфиге. Если магазин на 5000 товаров с ежедневным обменом с 1С стабильно не укладывается в лимиты, а поддержка хостинга каждый раз пишет про «нагрузку выше тарифной», вы просто выросли из shared-хостинга.

Российский VPS начального уровня стоит порядка 600-1500 рублей в месяц, и там таймауты и лимиты вы настраиваете сами. Разница с тарифом за 300 рублей несопоставима с потерями от сорванной синхронизации остатков в разгар сезона.

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

Короткий чек-лист

  1. Замерить time_total через curl. Круглая цифра означает таймаут, плавающая означает нагрузку.
  2. Найти в логе строку upstream timed out и понять, на каком слое рвётся.
  3. Включить request_slowlog_timeout = 5s и получить стек зависшей функции.
  4. Посмотреть SHOW FULL PROCESSLIST в момент проблемы.
  5. Уменьшить размер шага импорта до 20-50 записей.
  6. Отключить wp-cron из веб-запроса и повесить на крон хостинга.
  7. Тяжёлые разовые операции перенести в консоль.
  8. Внешним API поставить таймаут 8 секунд и кэш ответа.
  9. Лимиты поднимать в последнюю очередь, а не в первую.

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

Если 504 повторяется, а времени копаться в логах нет, напишите через форму на главной: диагностика с замерами и внятным заключением обычно занимает два-три часа, и по её итогам сразу видно, чинится это настройкой или требует переезда. Регулярные проверки времени отклика и лимитов входят в абонентское обслуживание, там такие вещи ловятся до того, как их заметит клиент. Остальные разборы по эксплуатации сайтов лежат в рубрике про поддержку.

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

Что означает ошибка 504 Gateway Timeout

Веб-сервер передал запрос обработчику и не дождался ответа за отведённое время. Соединение обрывается, а операция на сервере может продолжаться, и это отдельная проблема.

Где именно стоит таймаут

Их несколько: у веб-сервера свой, у PHP свой, у базы свой. Обрывает тот, что сработал первым. Поэтому увеличение одного лимита часто ничего не меняет.

Почему обмен с 1С падает на середине

Потому что выгрузка не укладывается в таймаут. Лечится разбиением на порции и запуском через консоль или очередь, а не увеличением лимита до бесконечности.

Что делать, если операция объективно долгая

Уносить её из веб-запроса: в фоновую задачу, в очередь или в консольный скрипт по расписанию. Всё, что дольше нескольких секунд, браузеру ждать не должен.

Передайте техническую часть в надёжные руки.

Слежу за хостингом, бэкапами и обновлениями. Когда что-то пойдёт не так – звоните мне, не своему программисту, не в спортзал и не маме.

Короткие вопросы по теме

Все вопросы по теме "Поддержка и абонентское обслуживание"

Заявка

Обсудим задачу

Напишите имя и телефон, выберите удобный канал. Перезвоню или напишу в течение часа в рабочее время.

Куда удобнее ответить?