Короткий ответ
502 значит: nginx жив и принял запрос, а апстрим не принял соединение или сразу закрыл его. Это не «сайт упал целиком» и не 504 (апстрим принял, но не ответил вовремя). На Ubuntu с PHP чаще всего мёртв php8.3-fpm / php8.2-fpm, не совпал путь unix-сокета в fastcgi_pass и listen пула, либо Node/gunicorn не слушает адрес из proxy_pass. Хост site.example на 10.0.20.40, корень /var/www/site.example.
Читайте error.log nginx: connect() to unix:/run/php/... failed. Пока этой строки нет — не крутите pm.max_children и не увеличивайте таймауты: таймауты дают 504.
Симптомы и как отличить
- Браузер:
502 Bad Gateway, страница nginx или NPM. curl -I https://site.example/→HTTP/1.1 502.- Access log: статус 502, время запроса близко к нулю (не 60 с).
| Код / лог | Смысл |
|---|---|
502, Connection refused | никто не слушает сокет/порт |
502, Permission denied (13) | nginx не может открыть сокет FPM |
504, upstream timed out | бэкенд жив, медленный |
| 500 в PHP | приложение, FPM ответил |
| connection refused на 80 | сам nginx мёртв |
Возможные причины
- Сервис
php8.3-fpmstopped/failed после обновления пакета. - В nginx
fastcgi_pass unix:/run/php/php8.2-fpm.sock, пул слушаетphp8.3-fpm.sock. - Пул слушает
127.0.0.1:9000, nginx стучится в unix (или наоборот). - Права сокета: владелец
root:root600, nginx в группеwww-dataне проходит. - Node на
127.0.0.1:3000упал,proxy_pass http://127.0.0.1:3000. - Upstream в Docker: nginx на хосте ходит на
localhost, контейнер в другой сети — см. NPM. proxy_next_upstreamисчерпал все бэкенды —no live upstreams.- Реже: worker nginx упёрся в
nofile, не открывает сокет.
Не путайте короткий fastcgi_read_timeout с 502: при таймауте чтения будет 504.
Диагностика
1. Подтвердить, что это 502 от nginx
curl -sI --resolve site.example:443:10.0.20.40 https://site.example/ | head
sudo tail -n 50 /var/log/nginx/error.log
sudo awk '$9==502 {c++} END {print c+0}' /var/log/nginx/access.logИщите connect() failed, upstream prematurely closed connection, no live upstreams.
2. Сокет и PHP-FPM
systemctl is-active php8.3-fpm php8.2-fpm
ls -l /run/php/
sudo grep -R fastcgi_pass /etc/nginx/sites-enabled /etc/nginx/conf.d
sudo grep -E '^listen|^user|^group|^listen.owner|^listen.group' /etc/php/8.3/fpm/pool.d/www.conf /etc/php/8.2/fpm/pool.d/www.conf 2>/dev/nullПути должны совпасть байт-в-байт. php8.3-fpm.sock ≠ php-fpm.sock.
3. TCP-апстрим (Node, gunicorn, Apache за nginx)
sudo grep -R proxy_pass /etc/nginx/sites-enabled
sudo ss -tlnp | grep -E ':3000|:8080|:9000'
curl -sv --max-time 3 http://127.0.0.1:3000/ >/dev/nullConnection refused на том же порту, что в proxy_pass — причина 502 найдена.
4. Права unix-сокета
namei -l /run/php/php8.3-fpm.sock
ps aux | grep '[n]ginx'
id www-datalisten.mode = 0660, listen.owner/group = www-data — обычная схема Ubuntu. Nginx worker должен быть в той же группе.
5. Не путать с 504
sudo grep -E 'timed out|502|504' /var/log/nginx/error.log | tailupstream timed out (110: Connection timed out) — статья про 504, не эта.
Решение
Сценарий A. FPM не запущен
sudo systemctl enable --now php8.3-fpm
systemctl is-active php8.3-fpm
ls -l /run/php/php8.3-fpm.sock
sudo nginx -t && sudo systemctl reload nginxЕсли unit failed — диагностика пула: полный диск, битый www.conf, лимит pm.start_servers.
Сценарий B. Несовпадение сокета
Выровняйте. Либо в nginx:
fastcgi_pass unix:/run/php/php8.3-fpm.sock;Либо в пуле /etc/php/8.3/fpm/pool.d/www.conf:
listen = /run/php/php8.3-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660После правки пула — systemctl restart php8.3-fpm (reload может не пересоздать сокет при смене пути). Затем nginx -t и reload.
Сценарий C. Permission denied на сокет
Не chmod 777 насовсем. Верните 0660 и группу www-data. Проверьте, что override в pool.d не ставит listen.acl_users вразрез с пакетом.
Сценарий D. Node/приложение не слушает
Поднимите процесс штатным unit/compose, проверьте ss. В proxy_pass указывайте тот адрес, который реально LISTEN. http://localhost:3000 у nginx в контейнере — это сеть контейнера, не хост.
upstream site_node {
server 127.0.0.1:3000;
keepalive 8;
}
server {
server_name site.example;
location / {
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://site_node;
}
}Сценарий E. Apache как бэкенд за nginx
ProxyPass/proxy_pass http://127.0.0.1:8080 при мёртвом apache2 даёт тот же 502. Сначала apache2ctl configtest и ss :8080.
Сценарий F. Ubuntu 22.04 vs 24.04 и пакет php-fpm
На 22.04 часто ещё жив php8.1-fpm или 8.2, на 24.04 в образах — 8.3. После do-release-upgrade nginx продолжает fastcgi_pass unix:/run/php/php8.1-fpm.sock, а unit уже php8.3-fpm. В ls /run/php/ будет новый сокет, старый исчезнет — классический 502 «после обновления ОС», не «после нагрузки». Не ставьте php8.3-fpm параллельно и не оставляйте оба enabled, если vhost ссылается только на один файл: оба пула съедят RAM, а ошибка останется.
Проверьте, что snippets/fastcgi-php.conf на Ubuntu не затирает ваш SCRIPT_FILENAME. Пакетный сниппет обычно корректен; ломают самописные fastcgi_param SCRIPT_FILENAME $request_filename при alias.
Как проверить, что проблема устранена
curl -sI --resolve site.example:443:10.0.20.40 https://site.example/ | head
sudo tail -n 20 /var/log/nginx/error.log
systemctl is-active php8.3-fpm nginxОжидание: не 502. 301/200/404 приложения — норма для этой проверки. Повторите POST/форму, если 502 был только на PHP-скриптах.
Если не помогло
- 502 только на длинных запросах: всё же таймаут, смотрите 504 и
request_timeв access.log. - 502 после деплоя: новый сокет, старый pid FPM, SELinux
httpd_t(не Ubuntu по умолчанию) — неsetenforce 0навсегда. - NPM в Docker: сеть и SSL разбираются отдельно.
upstream prematurely closedпри живом Node: приложение падает на запросе — это ближе к 500 бэкенда, читайте его stdout.
Профилактика
- В unit nginx
Requires=/Wants=не заменяют мониторинг FPM: алерт наsystemctl is-active php8.3-fpmи на долю 502. - Один канонический путь сокета в документации стенда.
- Healthcheck апстрима (
proxy_passна/health) лучше, чем надежда на первый запрос пользователя. - После
apt upgrade php8.3-fpmпроверяйте, не сменился ли sock.
FAQ
502 и 504 — увеличивать timeout?
Для 502 с Connection refused — нет. Timeout не поднимет мёртвый процесс.
Можно ли fastcgi_pass 127.0.0.1:9000 вместо сокета?
Да, если пул listen = 127.0.0.1:9000. Смешивать нельзя. Сокет на localhost чуть дешевле и не открывает TCP.
Почему локальный php -v работает, а сайт 502?
CLI php — не FPM. Сайт ходит только в пул.
502 только у NPM, напрямую к приложению 200
Сеть Docker / неверный Forward Hostname. Статья про NPM.
Apache отдаёт 502
ProxyErrorOverride и mod_proxy к FPM/Tomcat. Логика та же: мёртвый бэкенд, смотрите error.log Apache.