Коды 502/504/499 в связке Nginx и PHP‑FPM — частые спутники «зависаний» и недоответов. В большинстве случаев причина находится и лечится без смены стека: достаточно грамотно прочитать логи, сверить таймауты и ограничители, понять, где рвётся цепочка — на клиенте, в Nginx, на соединении к upstream или внутри PHP‑FPM/приложения.
Что означают 502, 504 и 499 в контексте Nginx
Коды выглядят похоже, но причины разные:
- 502 Bad Gateway — Nginx не получил корректный ответ от upstream (PHP‑FPM). Причины: upstream не слушает сокет/порт, умер, перегружен, отдал битые заголовки или оборвал соединение.
- 504 Gateway Timeout — Nginx не дождался ответа upstream. Причины: долгий PHP‑скрипт, блокировки БД, очередь в PHP‑FPM, слишком маленькие таймауты на стороне Nginx.
- 499 Client Closed Request — клиент сам закрыл соединение до ответа (мобильная сеть, нетерпеливый пользователь, промежуточный балансировщик/CDN). На сервере запрос часто продолжает выполняться.
Логи: где смотреть и что искать
Диагностика начинается с логов: error_log и access_log Nginx, а также логи и статус PHP‑FPM.
Где логи по умолчанию
Частые пути:
- Nginx: /var/log/nginx/error.log и /var/log/nginx/access.log
- PHP‑FPM: /var/log/php-fpm/error.log (или journalctl, если юнит под systemd)
Быстрые команды для последних записей:
tail -n 200 /var/log/nginx/error.log
journalctl -u nginx --since '10 minutes ago'
journalctl -u php-fpm --since '10 minutes ago'
Настройка подробного access_log для upstream
Чтобы понимать, где «тормозит», добавьте формат с таймингами:
log_format upstream_timing '$remote_addr - $request $status '
'rt=$request_time urt=$upstream_response_time '
'ustat=$upstream_status bytes=$body_bytes_sent';
access_log /var/log/nginx/access_upstream.log upstream_timing;
Как читать поля:
$request_time
— общее время на запрос (клиент ↔ Nginx ↔ upstream).$upstream_response_time
— время ответа upstream. Если-
, значит upstream не вызывался.$upstream_status
— HTTP‑код от upstream (или несколько кодов, если были ретраи).
Для 504 обычно $upstream_response_time
близок к fastcgi_read_timeout
, а $upstream_status
пустой или 504. Для 502 чаще видно короткий или отсутствующий urt
, а в error_log — конкретная причина.
Типичные сообщения в error_log Nginx
connect() failed (111: Connection refused) while connecting to upstream
PHP‑FPM не слушает сокет/порт или умер. Проверьте, запущен ли пул и совпадают ли путь/порт в конфигурации Nginx и PHP‑FPM.
upstream sent too big header while reading response header from upstream
Upstream прислал слишком большие заголовки (часто из‑за огромных cookies). Помогает увеличение fastcgi_buffers
/fastcgi_buffer_size
или уменьшение размера заголовков на стороне приложения.
upstream prematurely closed connection while reading response header from upstream
Upstream закрыл соединение. Возможные причины: падение воркера PHP‑FPM, превышение request_terminate_timeout
или max_execution_time
, аварийный kill (OOM).
Диагностика шаг за шагом
1) Проверяем соединяемость Nginx → PHP‑FPM
Сопоставьте, как Nginx ходит к PHP‑FPM: сокет или TCP‑порт. В Nginx это директива fastcgi_pass
. Убедитесь, что ресурс существует и разрешения корректны.
ss -ltnp | grep php-fpm
ls -l /run/php/ | grep fpm.sock
Если используете unix‑сокет, владелец/группа/права должны позволять пользователю Nginx читать/писать. Для TCP проверьте, что порт слушается и не закрыт firewall.
2) Смотрим статус PHP‑FPM и пул
Включите статус и ping в пуле PHP‑FPM:
pm.status_path = /status
ping.path = /ping
ping.response = pong
И пробросьте их через Nginx в отдельные location
только из внутренней сети. Это даст статистику по занятым воркерам и очереди.
3) Включаем slowlog и ловим «медленные» скрипты
Slowlog — лучший друг при 504. Включите и задайте порог:
request_slowlog_timeout = 5s
slowlog = /var/log/php-fpm/slowlog-www.log
В slowlog вы увидите backtrace: запрос к БД, HTTP‑клиент, файловую операцию, локи в приложении.
4) Ищем признаки OOM и перезапусков
Если система убивает PHP‑FPM из‑за нехватки памяти, Nginx получит 502. Посмотрите dmesg и journalctl на предмет OOM‑killer и crash‑лупов.
5) Репродукция и измерение
Сделайте минимальный скрипт с sleep(N)
, чтобы проверить связку и таймауты без влияния приложения. Так вы поймёте, кто «режет» долгие ответы — Nginx, PHP‑FPM или внешний балансировщик.
Таймауты: баланс фронта и бэкенда
Клиентские таймауты в Nginx
client_header_timeout
иclient_body_timeout
— сколько ждать заголовки/тело от клиента.send_timeout
— сколько ждать подтверждения при отправке ответа клиенту.keepalive_timeout
— время жизни keep‑alive соединения; слишком мало — лишние переподключения.
FastCGI таймауты
fastcgi_connect_timeout
— попытка соединения с PHP‑FPM. При малом значении и высоких нагрузках увидите 502.fastcgi_send_timeout
— сколько Nginx готов отправлять запрос upstream.fastcgi_read_timeout
— сколько Nginx ждёт ответ от PHP‑FPM. Малое значение даёт 504, даже если PHP ещё работает.
Важно: увеличивая fastcgi_read_timeout
, проверьте, что на стороне PHP‑FPM не короче request_terminate_timeout
, а в php.ini — max_execution_time
. Иначе PHP‑FPM убьёт запрос раньше, и вы увидите 502/504.
Буферы FastCGI
Если upstream отдаёт большие заголовки/части ответа, увеличьте буферы:
fastcgi_buffers
иfastcgi_buffer_size
— влияют на «upstream sent too big header» и устойчивость при большихSet-Cookie
.fastcgi_busy_buffers_size
— полезно под нагрузкой при стриминге ответа.
Для небольших проектов достаточно виртуального хостинга, а при росте нагрузки удобнее масштабироваться на VDS. Если планируете переезд без простоя — посмотрите чек‑лист по миграции: переезд сайта без простоя. Оптимизации PHP и сжатия на шаред‑хостинге мы разбирали тут: OPcache и Brotli на shared.

PHP‑FPM: пул, очередь и «ножницы» производительности
На уровне пулов решаются две проблемы: «не хватает воркеров, растёт очередь» и «воркеры упираются в память/CPU и умирают».
PM‑режим и размеры пула
pm = dynamic|ondemand|static
— чаще уместны dynamic или ondemand.pm.max_children
— максимум воркеров. При очереди и 504/499 на длинных запросах увеличьте в пределах памяти.pm.start_servers
,pm.min_spare_servers
,pm.max_spare_servers
— баланс холодного старта и пиков.pm.max_requests
— перезапуск воркеров после N запросов для борьбы с утечками.
Ограничения и убийство «подвисших» запросов
request_terminate_timeout
— жёсткий предел времени запроса в FPM; не делайте его меньше ожидаемого времени тяжёлых сценариев.php.ini: max_execution_time
— ограничение времени выполнения скрипта.memory_limit
— слишком высокий провоцирует OOM, слишком низкий — фатальные ошибки и 502.
Slowlog и статус
Включайте slowlog на низком пороге (3–5 секунд) и снимайте статус пула под нагрузкой — так вы увидите, упираетесь ли в pm.max_children
и есть ли длинная очередь.
Рабочие сценарии фиксов
Сценарий 1: 504 на отчётах/экспорте
Признаки: $upstream_response_time
≈ таймаут чтения в Nginx, в slowlog — долгие обращения к БД/внешним API.
- Временно увеличьте
fastcgi_read_timeout
до значения, покрывающего операцию. - Оптимизируйте: индексы БД, лимиты выборок, страничная генерация.
- Вынесите тяжёлые задачи в фон (очередь), отдавайте быстрый ответ с ID задачи и endpoint прогресса.
Сценарий 2: 502 с «upstream sent too big header»
Признаки: в error_log соответствующее сообщение при входе пользователя/после авторизации.
- Увеличьте буферы:
fastcgi_buffer_size 32k
,fastcgi_buffers 16 32k
(подберите под свои заголовки). - Сократите размер cookies/заголовков в приложении: не храните большие payload в cookie, чистите устаревшие флаги.
Сценарий 3: 502 «connect() failed (111)»
Признаки: PHP‑FPM недоступен по сокету/порту, пиковая нагрузка, высокая очередь.
- Проверьте соответствие
fastcgi_pass
иlisten
пула (путь к сокету или порт). - Увеличьте
pm.max_children
в рамках памяти, включитеpm.max_requests
, поднимитеlisten.backlog
. - Проверьте OOM‑killer; при необходимости снизьте
memory_limit
и/или добавьте RAM (на VDS это сделать проще).
Сценарий 4: Всплески 499 на мобильном трафике
Признаки: много 499 в access_log на долгих эндпоинтах; пользователи или прокси закрывают соединение раньше ответа.
- Сократите TTFB: кэшируйте GET, используйте fastcgi‑кэш для детерминированных страниц.
- Перенесите тяжёлые операции в фон, на фронтенде — прогресс/поллинг.
- Проверьте таймауты на балансировщике/CDN: они не должны быть короче
fastcgi_read_timeout
.
Пример базовой конфигурации Nginx (фрагменты)
Минимальные безопасные значения для старта. Подбирайте под свою нагрузку и размер ответов.
log_format upstream_timing '$remote_addr - $request $status '
'rt=$request_time urt=$upstream_response_time '
'ustat=$upstream_status bytes=$body_bytes_sent';
server {
listen 80;
access_log /var/log/nginx/access_upstream.log upstream_timing;
location ~ \.php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/run/php/php-fpm.sock;
fastcgi_connect_timeout 3s;
fastcgi_send_timeout 30s;
fastcgi_read_timeout 60s;
fastcgi_buffer_size 32k;
fastcgi_buffers 16 32k;
fastcgi_busy_buffers_size 64k;
}
location = /status { include fastcgi_params; fastcgi_pass unix:/run/php/php-fpm.sock; }
location = /ping { include fastcgi_params; fastcgi_pass unix:/run/php/php-fpm.sock; }
}
Пример пула PHP‑FPM (фрагменты)
Баланс запуска/экономии памяти/стабильности:
[www]
pm = dynamic
pm.max_children = 20
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 10
pm.max_requests = 500
listen = /run/php/php-fpm.sock
listen.owner = nginx
listen.group = nginx
listen.mode = 0660
listen.backlog = 1024
request_terminate_timeout = 120s
request_slowlog_timeout = 5s
slowlog = /var/log/php-fpm/slowlog-www.log
pm.status_path = /status
ping.path = /ping
ping.response = pong
Как отличить 502 от 504 «по логам»
В access_log с форматом из примера:
- 504:
rt
иurt
примерно равны и близки кfastcgi_read_timeout
. - 502: часто
urt
мало или отсутствует, а в error_log есть сообщение о разрыве/невозможности соединения или «too big header». - 499: статус 499 в access_log,
urt
может быть числом (upstream работал), но клиент ушёл раньше. В error_log это может не отражаться.
Практические советы по снижению риска 499
- Не держите долгие операции в HTTP‑запросе. Переносите в очередь и возвращайте быстрый ответ.
- Сократите размер ответа и TTFB: кэш, предзагрузка данных, ленивые вычисления.
- Проверьте
send_timeout
и скорость клиента: медленным клиентам полезен стриминг ответа, но следите за таймаутами. - В PHP учитывайте обрыв клиента (
connection_aborted()
) и прекращайте работу, если допустимо.
Чек‑лист перед релизом
- Включён отдельный access_log с таймингами upstream; считаете перцентили.
- error_log на уровне
warn
илиerror
в обычном режиме,info
— только для отладки. - Статус/пинг PHP‑FPM доступны изнутри инфраструктуры, а не публично.
- Включён slowlog и настроены пороги
request_slowlog_timeout
/request_terminate_timeout
. - Размер пула (
pm.max_children
) согласован с доступной памятью и профилем запросов. - Буферы FastCGI подходят под ваши заголовки и пики.
- Таймауты Nginx согласованы с ограничениями PHP‑FPM и внешних прокси.
Мини‑FAQ
Почему 502 сразу после деплоя?
Во время перезапуска PHP‑FPM или прогрева OPCache возможна кратковременная недоступность. Делайте graceful reload и health‑checks; держите часть воркеров «тёплыми».
Можно ли «вылечить» 504 только увеличением таймаута?
Только временно. fastcgi_read_timeout
поможет пережить пики, но первопричина — медленный код/БД/внешние API или узкий пул PHP‑FPM. Оптимизируйте и масштабируйте.
Откуда берётся 499 и нужно ли паниковать?
Это клиент сам закрыл соединение. Эпизодически — норма. Если растёт на важных эндпоинтах — сокращайте TTFB, добавляйте кэш и фоновую обработку, проверьте таймауты на балансировщике/CDN.
Итоги
502 — про недоступный/падающий upstream или битые заголовки, 504 — про истечение ожидания ответа, 499 — про уход клиента. Отличаем их по таймингам в access_log и сообщениям error_log. Устойчивость достигается балансом таймаутов Nginx/PHP‑FPM, правильным размером пула, slowlog‑диагностикой и архитектурными решениями: кэширование, очереди, разбиение тяжёлых задач. Если проект перерос начальные мощности — переход на VDS упростит масштабирование.