Главная » Веб-серверы и PHP » Ошибка 502 Bad Gateway и 504 Gateway Timeout: nginx и PHP-FPM

Ошибка 502 Bad Gateway и 504 Gateway Timeout: nginx и PHP-FPM

Вместо сайта белая страница с надписью «502 Bad Gateway» или «504 Gateway Timeout» и подписью nginx внизу. Обе ошибки выдаёт nginx, и обе означают, что проблема не в нём самом, а в бэкенде — программе, которой nginx передал запрос. На сервере сайтов это почти всегда PHP-FPM.

Коротко: 502 — PHP-FPM не ответил вообще: он не запущен, у nginx нет доступа к сокету, все процессы заняты и очередь переполнена, или процесс PHP умер посреди запроса. 504 — PHP-FPM принял запрос, но не успел ответить за fastcgi_read_timeout (по умолчанию 60 секунд). Причину почти всегда называет одна строка в /var/log/nginx/error.log, а подробности — журнал PHP-FPM.

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

Схема простая: nginx принимает запрос посетителя, видит, что это PHP, и передаёт его в PHP-FPM через unix-сокет (например, /run/php/php8.3-fpm.sock) или TCP-порт. Дальше возможны два сценария.

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

Отсюда первое правило диагностики: 502 — ищите, почему PHP-FPM недоступен или падает; 504 — ищите, почему PHP выполняется так долго.

Первая минута: служба, сокет, журнал

Имя службы зависит от системы. В Debian и Ubuntu для каждой версии PHP своя служба: php8.2-fpm в Debian 12, php8.3-fpm в Ubuntu 24.04, php8.4-fpm в Debian 13. В AlmaLinux и Rocky Linux — просто php-fpm. Ниже примеры для PHP 8.3 в Ubuntu.

systemctl status php8.3-fpm
ls -l /run/php/
tail -n 30 /var/log/nginx/error.log

В выводе systemctl status PHP-FPM показывает строку состояния:

     Active: active (running) since Sat 2026-11-07 09:12:03 MSK; 5h 10min ago
     Status: "Processes active: 5, idle: 0, Requests: 18342, slow: 0, Traffic: 2.40req/sec"

Здесь уже есть подсказка: idle: 0 — свободных процессов нет, все пять заняты. Если служба в состоянии failed, смотрите journalctl -u php8.3-fpm -n 50: обычно там ошибка в конфигурации пула после правки.

Где что лежит:

Debian, Ubuntu AlmaLinux, Rocky Linux
Пулы /etc/php/8.3/fpm/pool.d/*.conf /etc/php-fpm.d/*.conf
Сокет пула www /run/php/php8.3-fpm.sock /run/php-fpm/www.sock
Журнал PHP-FPM /var/log/php8.3-fpm.log /var/log/php-fpm/error.log
Проверка конфигурации php-fpm8.3 -t php-fpm -t

Что пишет nginx в error.log

Типичная строка выглядит так:

2026/11/07 14:22:31 [crit] 2051#2051: *8812 connect() to unix:/run/php/php8.3-fpm.sock failed (2: No such file or directory) while connecting to upstream, client: 198.51.100.7, server: example.com, request: "GET / HTTP/1.1", upstream: "fastcgi://unix:/run/php/php8.3-fpm.sock:", host: "example.com"

Важна часть между адресом сокета и словом client. Обратите внимание на уровень: ошибки «нет файла» и «нет прав» nginx пишет как [crit], остальные — как [error]. Если для error_log задан уровень crit, сообщения уровня [error] в журнал не попадут — для диагностики оставьте уровень по умолчанию (error). Расшифровка:

Строка в error.log Что это значит Что делать
connect() to unix:… failed (2: No such file or directory) Сокета нет: PHP-FPM не запущен или путь в fastcgi_pass не совпадает с listen пула Запустить службу, сверить путь (часто после смены версии PHP)
connect() to unix:… failed (13: Permission denied) У пользователя nginx нет прав на сокет Проверить listen.owner, listen.group, listen.mode; в AlmaLinux — listen.acl_users и SELinux
connect() to … failed (111: Connection refused) Адрес есть, но никто не слушает: PHP-FPM упал или слушает другой порт Проверить службу и ss -xlp | grep php или ss -tlnp
connect() to unix:… failed (11: Resource temporarily unavailable) Очередь сокета переполнена: все процессы заняты, новые запросы не помещаются Найти, чем заняты процессы; при необходимости поднять pm.max_children
upstream prematurely closed connection while reading response header from upstream Процесс PHP завершился посреди запроса: убит по request_terminate_timeout, упал или убит OOM killer Смотреть журнал PHP-FPM и journalctl -k
upstream sent too big header while reading response header from upstream Заголовки ответа (часто cookies) больше буфера Увеличить fastcgi_buffer_size и fastcgi_buffers
upstream timed out (110: Connection timed out) while reading response header from upstream Это 504: PHP не ответил за fastcgi_read_timeout Искать медленный код или запрос, смотреть slowlog

Про код 11 стоит сказать отдельно. Для unix-сокетов Linux возвращает EAGAIN вместо «соединение отклонено», когда очередь ожидающих соединений заполнена, — это прямо отмечено в исходном коде nginx. То есть ошибка означает не сломанный PHP-FPM, а перегруженный.

Журнал PHP-FPM: max_children и таймауты

grep -E 'max_children|timed out|exited on signal' /var/log/php8.3-fpm.log | tail -n 20
[07-Nov-2026 14:20:05] WARNING: [pool www] server reached pm.max_children setting (5), consider raising it
[07-Nov-2026 14:21:41] WARNING: [pool www] child 20411, script '/var/www/example.com/index.php' (request: "POST /index.php") execution timed out (90.004123 sec), terminating
[07-Nov-2026 14:21:41] WARNING: [pool www] child 20411 exited on signal 15 (SIGTERM) after 412.336981 seconds from start

Первая строка — пул упёрся в pm.max_children: новые запросы ждут в очереди сокета, а когда она переполнится, nginx начнёт отдавать 502. При pm = ondemand текст чуть другой — server reached max_children setting, без pm., поэтому в grep выше ищется просто max_children. Вторая и третья строки — запрос выполнялся дольше request_terminate_timeout, и PHP-FPM завершил процесс. Посетитель в этот момент получил 502.

Параметры пула, от которых зависят 502 и 504

pm и pm.max_children

Параметр pm задаёт режим управления процессами:

  • static — всегда ровно pm.max_children процессов;
  • dynamic — число меняется между pm.min_spare_servers и pm.max_spare_servers свободных процессов, но не больше pm.max_children;
  • ondemand — процессы запускаются при поступлении запросов и завершаются после простоя pm.process_idle_timeout (по умолчанию 10 секунд).

pm.max_children — предел одновременно обрабатываемых запросов. В Debian и Ubuntu в стандартном пуле www он равен 5 — для рабочего сайта обычно мало. Но поднимать его наугад опасно: если процессам не хватит памяти, сервер уйдёт в своп, и станет только хуже. Прикиньте по памяти. Средний размер процесса:

ps -C php-fpm8.3 -o rss= | awk '{s+=$1; n++} END {printf "процессов: %d, в среднем: %.0f МБ\n", n, s/n/1024}'
процессов: 6, в среднем: 74 МБ

В AlmaLinux процесс называется php-fpm. RSS учитывает и общую память (например, opcache), поэтому оценка получается с запасом. Дальше считаем: из памяти сервера вычитаем то, что нужно MySQL, системе и кэшу файлов, остаток делим на средний размер процесса. Например, на VDS с 4 ГБ, где MySQL занимает около 1 ГБ, под PHP можно отдать примерно 2 ГБ — это около 27 процессов по 74 МБ, и pm.max_children = 25 будет разумным значением.

Помните: если процессы заняты ботами или медленными запросами, больший pm.max_children только отложит 502. Сначала выясните, чем они заняты.

Таймауты: max_execution_time, request_terminate_timeout, fastcgi_read_timeout

На время выполнения запроса влияют три настройки, и их стоит согласовать:

Параметр Где Что делает
max_execution_time php.ini или пул PHP сам прерывает скрипт с ошибкой Maximum execution time of N seconds exceeded
request_terminate_timeout пул PHP-FPM PHP-FPM завершает процесс, если запрос идёт дольше; по умолчанию 0 — выключено
fastcgi_read_timeout nginx Сколько nginx ждёт данных от PHP-FPM между двумя чтениями; по умолчанию 60 с, после — 504

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

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

; /etc/php/8.3/fpm/pool.d/www.conf
request_terminate_timeout = 120s
request_slowlog_timeout = 10s
slowlog = /var/log/php8.3-fpm-slow.log
# nginx, location с fastcgi_pass
fastcgi_read_timeout 150s;

Увеличивать fastcgi_read_timeout имеет смысл для отдельных долгих операций (импорт, отчёты), лучше в отдельном location, а не для всего сайта. Описание директивы — в документации nginx.

slowlog: что именно тормозит

Если задан request_slowlog_timeout, PHP-FPM для каждого запроса дольше этого времени записывает в slowlog стек вызовов PHP. В основном журнале появляется строка executing too slow (…), logging, а в slowlog видно, на какой функции и в каком файле плагина или темы висел запрос. Для 504 это самый быстрый способ найти виновника.

Статус-страница PHP-FPM

Чтобы видеть загрузку пула в любой момент, включите статус-страницу в пуле и перезапустите PHP-FPM:

pm.status_path = /fpm-status

Запросить её можно напрямую через сокет утилитой cgi-fcgi (пакет libfcgi-bin в Debian и Ubuntu, fcgi из EPEL в AlmaLinux), не трогая nginx:

SCRIPT_NAME=/fpm-status SCRIPT_FILENAME=/fpm-status REQUEST_METHOD=GET \
  cgi-fcgi -bind -connect /run/php/php8.3-fpm.sock
pool:                 www
process manager:      dynamic
start time:           07/Nov/2026:09:12:03 +0300
start since:          18624
accepted conn:        18342
listen queue:         12
max listen queue:     128
listen queue len:     511
idle processes:       0
active processes:     5
total processes:      5
max active processes: 5
max children reached: 3
slow requests:        0

Главное здесь: listen queue — сколько запросов ждут свободный процесс прямо сейчас, max children reached — сколько раз пул упирался в предел, slow requests — счётчик медленных запросов. Ненулевая очередь при idle processes: 0 — пул не справляется. Если открываете статус через nginx, ограничьте доступ (allow 127.0.0.1; deny all;): страница не должна быть видна из интернета.

Как исправить: порядок действий

  1. Проверьте конфигурацию перед перезапуском: php-fpm8.3 -t (ответ configuration file … test is successful) и nginx -t.
  2. Нет сокета или отказ в соединении — запустите службу и сверьте путь в fastcgi_pass с listen пула. После обновления PHP старый сокет часто остаётся прописан в конфигурации сайта; о версиях и путях — в статье Несколько версий PHP и PHP-FPM на одном сервере.
  3. Permission denied — в Debian и Ubuntu nginx работает от www-data, и у сокета пула должны быть listen.owner/listen.group, дающие ему доступ. В AlmaLinux nginx работает от nginx, а доступ задаёт listen.acl_users = apache,nginx; если сокет перенесён в нестандартное место, проверьте SELinux: ausearch -m avc -ts recent.
  4. Код 11 и max_children — по статус-странице и журналу access.log выясните, чем заняты процессы. Если это живые посетители и память позволяет, поднимите pm.max_children.
  5. Too big header — увеличьте буферы: fastcgi_buffer_size 32k; и fastcgi_buffers 16 16k;.
  6. 504 — включите slowlog, найдите медленный код или SQL-запрос, а таймауты поднимайте только точечно.

Случай из практики

После перезагрузки одного из серверов я проверял скорость сайта. Замер через curl -o /dev/null -s -w '%{time_total}\n' https://example.com/ показала почти 6 секунд на первый запрос, а следующие обрабатывались примерно за 0,4 секунды. Ни 502, ни 504 не было, в журналах — тоже ничего.

Пул был настроен с pm = ondemand: после перезапуска в нём нет ни одного рабочего процесса, и PHP-FPM поднимает их по первым запросам. Но главное время уходило на «холодный» старт: opcache после перезапуска PHP-FPM пуст, и при первом обращении PHP заново компилирует все файлы движка, а после перезагрузки сервера ещё и кэш файлов и буферы MySQL пусты. Со второго запроса байт-код уже лежит в opcache, и время падает до обычного. Это не ошибка и не повод менять тариф. Кроме того, страничный кэш отдаёт готовые страницы без запуска PHP, поэтому большинство посетителей «холодного» запроса вообще не замечают.

Как не допустить повторения

  • Рассчитайте pm.max_children по памяти и проверяйте статус-страницу при росте посещаемости.
  • Держите включённым request_slowlog_timeout — медленный код будет виден до того, как дойдёт до 504.
  • Включите страничный кэш и opcache: чем меньше запросов доходит до PHP, тем реже пул упирается в предел. Пошаговая настройка связки — в статье Настройка VPS для быстрой работы сайта WordPress.
  • Закрывайте на уровне nginx то, что сайту не нужно и что любят боты, — например, xmlrpc.php в WordPress.
  • После правки конфигурации всегда запускайте php-fpm8.3 -t и nginx -t, а после перезапуска откройте сайт сами.

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

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

Чаще всего пул ненадолго упирается в pm.max_children: в пиковые минуты все процессы заняты, очередь сокета переполняется, и часть запросов получает 502 с кодом 11 в error.log. Ищите в журнале PHP-FPM строку server reached … max_children и сопоставьте время с access.log — нередко пик создают боты.

Достаточно увеличить fastcgi_read_timeout, чтобы убрать 504?

Ошибка исчезнет, но страница будет открываться так же долго, а процессы PHP будут дольше заняты, и при наплыве посетителей появятся 502. Поднимать таймаут стоит только для конкретных долгих операций. В остальных случаях найдите причину через slowlog и журнал медленных запросов MySQL.

Чем reload PHP-FPM отличается от restart?

В Debian и Ubuntu systemctl reload php8.3-fpm отправляет мастер-процессу сигнал USR2: он перечитывает конфигурацию и перезапускает рабочие процессы, не закрывая сокет. Restart останавливает службу полностью, и запросы, пришедшие в этот момент, получают 502. В обоих случаях opcache очищается, поэтому первые запросы после этого выполняются медленнее.

Ошибка 502 бывает, когда nginx работает как прокси, а не с PHP-FPM?

Да, логика та же: 502 — бэкенд недоступен или вернул некорректный ответ, 504 — не ответил вовремя. Разница только в директивах: вместо fastcgi_read_timeout — proxy_read_timeout, а в error.log в поле upstream будет адрес вида http://127.0.0.1:8080.