Короткий ответ

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 мёртв

Возможные причины

  1. Сервис php8.3-fpm stopped/failed после обновления пакета.
  2. В nginx fastcgi_pass unix:/run/php/php8.2-fpm.sock, пул слушает php8.3-fpm.sock.
  3. Пул слушает 127.0.0.1:9000, nginx стучится в unix (или наоборот).
  4. Права сокета: владелец root:root 600, nginx в группе www-data не проходит.
  5. Node на 127.0.0.1:3000 упал, proxy_pass http://127.0.0.1:3000.
  6. Upstream в Docker: nginx на хосте ходит на localhost, контейнер в другой сети — см. NPM.
  7. proxy_next_upstream исчерпал все бэкенды — no live upstreams.
  8. Реже: 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.sockphp-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/null

Connection refused на том же порту, что в proxy_pass — причина 502 найдена.

4. Права unix-сокета

namei -l /run/php/php8.3-fpm.sock
ps aux | grep '[n]ginx'
id www-data

listen.mode = 0660, listen.owner/group = www-data — обычная схема Ubuntu. Nginx worker должен быть в той же группе.

5. Не путать с 504

sudo grep -E 'timed out|502|504' /var/log/nginx/error.log | tail

upstream 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.