nginx 502 Bad Gateway: причины и способы устранения
502 Bad Gateway означает: nginx не получил от бэкенда корректного ответа. Пять самых частых причин, нужная строка в логе ошибок и как доказать, что исправление действительно сработало.
Что на самом деле означает «502 Bad Gateway»
Ошибка 502 приходит не от вашего приложения, а от nginx. nginx принял запрос, передал его бэкенду (PHP-FPM, Node, Python, другой веб-сервер) и не получил оттуда пригодного ответа. Именно поэтому на странице ошибки нет ничего полезного.
Разграничение с соседними кодами экономит в критической ситуации много времени:
- 500 Internal Server Error: бэкенд ответил, но ответом была ошибка. Причина лежит в коде приложения. Читайте лог приложения, а не лог nginx.
- 502 Bad Gateway: соединение с бэкендом не установилось или оборвалось до того, как пришёл полный ответ.
- 504 Gateway Time-out: соединение стояло, бэкенд просто слишком долго молчал, и у nginx кончилось терпение.
Это различие даёт больше всего при разборе таймаутов, потому что одна и та же медленная страница выглядит то как 502, то как 504, в зависимости от того, кто первым обрывает соединение. Подробнее об этом ниже.
Состояние пакетов в Debian 13, Debian 12, Ubuntu 24.04 и Ubuntu 22.04
При ошибках 502 nginx ведёт себя во всех четырёх системах одинаково, директивы называются идентично. Различия почти целиком лежат на стороне PHP, и именно из них возникает большинство ошибок 502 после смены дистрибутива.
| Система | nginx | PHP | Имя службы | Сокет |
| Debian 13 (Trixie) | 1.26.3 | 8.4 | php8.4-fpm | /run/php/php8.4-fpm.sock |
| Debian 12 (Bookworm) | 1.22.1 | 8.2 | php8.2-fpm | /run/php/php8.2-fpm.sock |
| Ubuntu 24.04 LTS | 1.24.0 | 8.3 | php8.3-fpm | /run/php/php8.3-fpm.sock |
| Ubuntu 22.04 LTS | 1.18.0 | 8.1 | php8.1-fpm | /run/php/php8.1-fpm.sock |
Во всех дальнейших командах заменяйте номер версии на тот, что стоит в вашей системе. Все примеры рассчитаны на shell от имени root, иначе ставьте перед командой sudo. Какая версия установлена, покажет взгляд на бинарные файлы FPM, даже если служба вообще не стартует:
ls /usr/sbin/php-fpm*
ls /etc/php/
Сначала лог ошибок: как найти нужную строку
Самая частая ошибка при диагностике: искать не в том логе. У nginx есть глобальный лог ошибок и часто отдельный лог на каждый виртуальный хост. Какой файл действует, написано в конфигурации:
grep -Rn "error_log" /etc/nginx/nginx.conf /etc/nginx/sites-enabled/
Обратите внимание на заглавную -R. В каталоге /etc/nginx/sites-enabled/ на Debian и Ubuntu лежат исключительно символьные ссылки на sites-available, а GNU grep с маленькой -r при рекурсивном обходе по символьным ссылкам не идёт. С -rn вы получите поэтому только совпадения из nginx.conf, тогда как собственная строка error_log виртуального хоста останется невидимой: как раз та, которую при ошибке 502 и ищут, потому что глобальный лог не содержит ошибку FastCGI, как только vhost перенаправляет запись. Кто предпочитает остаться при -r, тот ищет grep-ом прямо в исходных каталогах:
grep -rn "error_log" /etc/nginx/nginx.conf /etc/nginx/sites-available/ /etc/nginx/conf.d/
Без собственного указания в блоке server всё попадает в /var/log/nginx/error.log. Самый надёжный путь к нужной строке идёт через живую запись: держите лог открытым в одном терминале, во втором вызывайте запрос и смотрите на строки, которые при этом появляются заново.
tail -f /var/log/nginx/error.log
Как вариант, фильтруйте по отметке времени. nginx пишет локальное время в формате 2026/07/26 09:14:22, а не UTC. Сверка с часами в другом часовом поясе регулярно даёт сбой.
Строка про 502 всегда построена по одному и тому же образцу. Пример:
2026/07/26 09:14:22 [error] 812#812: *3 connect() to unix:/run/php/php8.2-fpm.sock
failed (2: No such file or directory) while connecting to upstream,
client: 203.0.113.7, server: example.com,
request: "GET /index.php HTTP/1.1",
upstream: "fastcgi://unix:/run/php/php8.2-fpm.sock:", host: "example.com"
Всю информацию несут четыре составные части:
- Системный вызов:
connect(),recv(),send().connect()означает, что соединение так и не состоялось.recv()означает, что соединение стояло, а затем оборвалось. - Номер ошибки в скобках, см. таблицу ниже. Это и есть собственно диагноз.
- Фаза:
while connecting to upstreamпротивwhile reading response header from upstream. Первое: проблема доступности, второе: проблема времени выполнения или падение процесса. - Поле
upstream:. Там стоит путь или адрес, который nginx действительно использовал. Не то, что вы предполагаете в конфигурации, а то, что реально загружено.
| Сообщение | Значение | Раздел |
| 2: No such file or directory | Файла сокета не существует | Служба не работает или путь неверен |
| 13: Permission denied | Сокет есть, но nginx не имеет к нему доступа | Права |
| 111: Connection refused | Ничто не слушает на этом адресе и порту | Бэкенд недоступен |
| 110: Connection timed out | Нет ответа в отведённый срок | Таймаут |
| 104: Connection reset by peer | Процесс бэкенда умер посреди запроса | Падения и лимиты |
| 11: Resource temporarily unavailable | Очередь сокета переполнена | Падения и лимиты |
Строка про 13: Permission denied у nginx часто идёт с уровнем [crit] вместо [error]. Кто фильтрует только по [error], тот её пропустит. Фильтруйте лучше по тексту:
grep -n "upstream" /var/log/nginx/error.log
Вторая половина правды стоит в логе PHP-FPM, по умолчанию в /var/log/php8.2-fpm.log. При падениях и упёршихся лимитах там написано обоснование, тогда как nginx видит только симптом.
Причина 1: PHP-FPM не запущен
Классический номер ошибки 2. Проверьте сначала состояние службы:
systemctl is-active php8.2-fpm
systemctl status php8.2-fpm --no-pager -l
is-active отвечает одним словом, этого достаточно для скрипта. Если приходит inactive или failed, берите причину из журнала, причём с временным окном вместо последних десяти строк:
journalctl -u php8.2-fpm --since "30 min ago" --no-pager
Очень часто причиной оказывается сломанная конфигурация пула, оставшаяся после reload. У FPM есть собственная проверка синтаксиса, которая работает без перезапуска:
php-fpm8.2 -t
Типичные ошибки запуска дословно и что они означают:
ERROR: [pool www] cannot get uid for user 'webuser': системного пользователя, записанного вuser =, больше не существует, например после миграции.ERROR: unable to bind listening socket for address '/run/php/php8.2-fpm.sock': No such file or directory (2): каталог/run/phpотсутствует. Он лежит на tmpfs и создаётся при старте службы. Кто направляетlistenна путь за пределами этого каталога, тот должен позаботиться о создании каталога сам.ERROR: An another FPM instance seems to already listen on ...: процесс из неудавшегося перезапуска всё ещё висит на сокете.
Как понять, что проблема действительно решена: не по тому, что systemctl restart отработал без вывода. Мастер-процесс FPM стартует и в том случае, когда ни один рабочий процесс не способен принимать запросы. Показательно другое: сокет виден в системе, и FPM на него отвечает.
ss -lx | grep php
Для настоящей проверки ответа включите в /etc/php/8.2/fpm/pool.d/www.conf строку ping.path = /ping, перезагрузите FPM и обратитесь к сокету напрямую, полностью в обход nginx:
apt-get install -y libfcgi-bin
SCRIPT_NAME=/ping SCRIPT_FILENAME=/ping REQUEST_METHOD=GET \
cgi-fcgi -bind -connect /run/php/php8.2-fpm.sock
Если в ответ приходит pong, сторона PHP в порядке и ошибка лежит между nginx и сокетом. Если не приходит ничего, искать дальше в nginx незачем.
Причина 2: неверный путь к сокету
На Debian и Ubuntu это с большим отрывом самая частая причина, потому что имя сокета содержит версию PHP, конфигурация nginx это имя жёстко прописывает, а обновление дистрибутива разводит их в разные стороны.
Конкретно: обновление с Debian 12 на Debian 13 поднимает PHP с 8.2 до 8.4. Старый сокет /run/php/php8.2-fpm.sock исчезает, а в файле vhost он остаётся. Результат: 502 на каждой без исключения PHP-странице, сразу после перезагрузки. То же самое происходит при переходе с Ubuntu 22.04 на 24.04 (с 8.1 на 8.3).
Вторая ловушка сидит в поставляемой конфигурации-примере. В /etc/nginx/sites-available/default есть закомментированный блок, чей fastcgi_pass указывает на версию PHP, которая уже много лет не актуальна. Кто просто раскомментирует эти строки, тот ошибку 502 этим и создаёт.
Сравните обе стороны. Что хочет использовать nginx:
grep -Rn "fastcgi_pass" /etc/nginx/
Что на самом деле предлагает FPM:
grep -n "^listen *=" /etc/php/*/fpm/pool.d/*.conf
Добавка *= в шаблоне поиска сделана намеренно: она требует после listen любое число пробелов и затем знак равенства. Простое ^listen попадает ведь ещё и в listen.owner, listen.group и listen.mode, и нужная строка теряется среди совпадений. Шаблон /etc/php/*/, наоборот, стоит правильно, он охватывает каждую установленную версию PHP. И что существует в работающей системе:
ls -l /run/php/
Все три вывода должны показывать один и тот же путь. Важно: grep -R по всему /etc/nginx/, а не только по тому единственному файлу, который у вас на подозрении. Подключённые через include фрагменты: излюбленное укрытие, равно как и старые файлы в sites-available, которые всё ещё активны через забытую символьную ссылку в sites-enabled. И здесь тоже действует правило: только заглавная -R идёт по этим ссылкам и показывает вам, какой файл действительно активен.
ls -l /etc/nginx/sites-enabled/
Прежде чем что-то менять, сделайте копию. Это стоит двух секунд и в спорном случае избавляет от восстановления из резервной копии:
mkdir -p /root/backups
cp -a /etc/nginx/sites-available/default /root/backups/default.bak
Кто вмешивается многократно, тому лучше добавить отметку времени (default.bak.$(date +%F-%H%M)), ведь cp -a перезаписывает существующий .bak без единого слова.
После исправления всегда сначала тест, потом загрузка конфигурации. reload вместо restart, чтобы не рвать существующие соединения:
nginx -t
systemctl reload nginx
Учтите: nginx -t проверяет исключительно синтаксис. Несуществующий путь к сокету считается совершенно корректной конфигурацией. Зелёное syntax is ok поэтому не доказывает, что ошибка 502 исчезла.
Причина 3: права на сокет
Номер ошибки 13. Сокет на месте, только nginx не может его открыть. На Debian и Ubuntu nginx работает от пользователя www-data, и стандартный пул FPM создаёт сокет соответствующим образом. Видно это в файле пула:
grep -n "listen.owner\|listen.group\|listen.mode" /etc/php/*/fpm/pool.d/*.conf
С listen.owner = www-data, listen.group = www-data и режимом 0660 связка работает без вмешательства. Пойти не так может в трёх ситуациях:
- Отдельный пул на каждый проект. Если
userиgroupвыставлены на пользователя проекта,listen.groupвсё равно должна быть группой, в которую входит nginx. Обычная связка:listen.owner = projektuserвместе сlisten.group = www-data. - Сокет за пределами /run. Доступным должен быть не только файл сокета: каждому каталогу на пути к нему нужно право на выполнение для nginx. Сокет в домашнем каталоге с режимом 0700 недоступен никому, кроме владельца.
- nginx с изменённым
userв/etc/nginx/nginx.conf.
Подозрение подтверждается без гадания: попробуйте доступ ровно от того пользователя, которому он нужен в работе:
id www-data
sudo -u www-data test -w /run/php/php8.2-fpm.sock && echo "доступ есть" || echo "доступа нет"
Ещё показательнее вызов cgi-fcgi из предыдущего раздела, тоже с sudo -u www-data впереди. Если FPM отвечает от root, но не от www-data, диагноз однозначен.
Выставляйте значения в файле пула, а не через chmod на файле сокета. chmod 666 держится ровно до следующего перезапуска FPM, затем FPM создаёт сокет заново с настроенными правами, и ошибка возвращается, обычно в самый неудобный момент.
На Debian и Ubuntu активен AppArmor. Поставляемый профиль для nginx в состоянии поставки принудительно не применяется, но мог быть включён шаблонами усиления безопасности. Если сообщение с номером 13 остаётся несмотря на корректные права, стоит заглянуть в aa-status и в journalctl -k | grep DENIED.
Причина 4: таймаут при долгих запросах
Здесь аккуратный подход отделяется от гадания, ведь простое истечение срока nginx порождает 504, а не 502. Если долгий запрос заканчивается ошибкой 502, почти всегда PHP-FPM снял рабочий процесс раньше, и nginx увидел лишь оборванное соединение. В логе тогда типично стоит:
recv() failed (104: Connection reset by peer) while reading response header from upstream
Одновременно действуют три срока, и их порядок решает, какой код статуса вы получите:
max_execution_timeвphp.ini, по умолчанию 30 секунд в режиме FPM. Считает только время выполнения скрипта. Ожидание в системных вызовах, например на зависшем запросе к базе данных, под Linux в этот счёт не входит. Поэтому это значение не спасает как раз в той проблеме, в которой его и ждут.request_terminate_timeoutв файле пула, в состоянии поставки выключен. Завершает рабочий процесс жёстко, независимо от того, на чём тот висит. Это и есть значение, которое производит ошибки 502.fastcgi_read_timeoutв nginx, по умолчанию 60 секунд. Когда он истекает, приходит 504.
Пригодный порядок идёт по возрастанию изнутри наружу, чтобы первым всегда срабатывал тот слой, который ещё способен выдать понятное сообщение об ошибке. Например 60, затем 75, затем 90 секунд. При обратной сортировке вы получите ошибки 502 вместо читаемых ошибок PHP.
grep -rn "request_terminate_timeout" /etc/php/*/fpm/pool.d/*.conf
Что именно снял FPM, стоит в его логе открытым текстом:
WARNING: [pool www] child 1234, script '/var/www/html/import.php'
(request: "POST /import.php") execution timed out (76.271849 sec), terminating
Прежде чем поднимать сроки, дайте себе показать, куда уходит время. У FPM есть для этого собственный лог, который при превышении пишет полный стек вызовов PHP. Включается в файле пула:
slowlog = /var/log/php-fpm-slow.log
request_slowlog_timeout = 5s
После systemctl reload php8.2-fpm там при следующем медленном запросе появится функция вместе с номером строки, на которой всё висит. На практике в четырёх случаях из пяти это запрос к базе данных без индекса или обращение к чужому программному интерфейсу без собственного срока. Накрутка сроков тогда лишь удлиняет время до ошибки и вдобавок блокирует рабочие процессы.
Причина 5: бэкенд недоступен
Касается любого бэкенда, к которому обращаются по TCP: FPM на порту 9000, приложение на Node, служба на Java, контейнер. Ведущее сообщение: номер ошибки 111.
connect() to 127.0.0.1:3000 failed (111: Connection refused) while connecting to upstream
Проверьте сначала, слушает ли вообще что-нибудь, и главное на чём:
ss -ltnp
Эта команда перечисляет исключительно TCP-сокеты, и отсюда возникает распространённое заблуждение: пул PHP-FPM в состоянии поставки слушает на Unix-сокете в /run/php/ и в этом списке не появляется вообще, хотя работает безупречно. Увидеть его можно только так:
ss -lxn | grep php-fpm
ls -l /run/php/
Для FPM команда ss -ltnp показательна, следовательно, только тогда, когда пул сознательно переведён на TCP через listen = 127.0.0.1:9000. Для бэкендов на Node, на Java или в контейнерах это, наоборот, ровно та команда, что нужна.
Три подводных камня, которые редко попадают в руководства:
- localhost разрешается сначала в ::1. Если в nginx стоит
proxy_pass http://localhost:3000;, а приложение слушает только на127.0.0.1, nginx пробует адрес IPv6 и получает «Connection refused». Служба работает, порт открыт, и всё равно 502. Решение: прописать в nginx127.0.0.1явно или привязать приложение к обоим семействам адресов. - nginx разрешает имена однократно при загрузке. Если в
proxy_passстоит имя хоста, nginx запоминает адрес. Если бэкенд меняет IP-адрес, например заново запущенный контейнер, запросы уходят в пустоту до следующегоreload. - Файрвол на обратном пути. Если бэкенд стоит на другом сервере, вместо «Connection refused» появляется
113: No route to hostили таймаут. Проверяйте черезufw statusи прямым тестом соединения с самого сервера nginx.
Если используется блок upstream с несколькими целями, добавляется собственное сообщение:
no live upstreams while connecting to upstream
Это значит, что nginx после повторных неудачных попыток вывел все цели из обращения на время fail_timeout. Даже после починки бэкенда придётся тогда ждать истечения этого срока, прежде чем запросы снова пойдут. systemctl reload nginx сбрасывает это состояние немедленно.
Ошибка 502, которая не подходит ни к одной из пяти причин
Два случая выглядят как отказ, но отказом не являются, и поэтому стоят непропорционально много времени.
Слишком большой заголовок ответа. Приложение работает безупречно, только отдельные запросы отдают 502:
upstream sent too big header while reading response header from upstream
Причиной служат крупные cookie или признаки сессии в заголовках, буфер nginx при этом слишком мал. В блоке server или location:
fastcgi_buffer_size 32k;
fastcgi_buffers 8 16k;
fastcgi_busy_buffers_size 64k;
При proxy_pass директивы называются proxy_buffer_size и proxy_buffers. Типично, что затронуты только вошедшие в систему пользователи, тогда как стартовая страница грузится безупречно.
Рабочие процессы кончились. Под нагрузкой в логе FPM появляется:
WARNING: [pool www] server reached pm.max_children setting (5), consider raising it
Новые запросы ждут тогда в очереди сокета. Если и она заполнена, nginx сообщает 11: Resource temporarily unavailable. Прежде чем повышать pm.max_children, коротко посчитайте: доступная оперативная память, делённая на реальное потребление одного рабочего процесса. Слишком высокое значение меняет ошибки 502 на состояние системы, в котором заканчивается память, а это бьёт уже и по базе данных.
Падения. Строки с exited on signal 11 (SIGSEGV) указывают на неисправное расширение PHP, часто после смены версии PHP с оставшимися модулями от предыдущей.
Если вмешательство не помогает: путь назад
Два правила держат ущерб небольшим. Во-первых: по одному изменению за раз, с копией оригинального файла в /root/backups, никогда в веб-каталоге. Во-вторых: перепроверять после каждого шага, вместо того чтобы менять три вещи одновременно и потом не знать, что именно помогло.
Если nginx после изменения отваливается совсем, верните копию на место и перезагрузите конфигурацию:
cp -a /root/backups/default.bak /etc/nginx/sites-available/default
nginx -t
systemctl reload nginx
Если nginx после restart больше не стартует, systemctl status nginx говорит редко когда достаточно. Показательнее:
journalctl -u nginx --since "10 min ago" --no-pager
Самая частая причина неудавшегося restart при синтаксически безупречной конфигурации: занятый порт 80 или 443, обычно процесс из предыдущего запуска. ss -ltnp | grep ':80' показывает виновника.
Как понять, что проблема действительно устранена
Команда без сообщения об ошибке не доказывает ровным счётом ничего. systemctl reload молчит и тогда, когда по сути ничего не изменилось, а nginx -t проверяет только синтаксис. Надёжны эти четыре доказательства:
- Запросить код статуса прямо на сервере, чтобы результат не искажали ни кэш, ни стоящая перед сервером служба:
Ожидается 200, а не 502. При нескольких виртуальных хостах передавайте имя:curl -sS -o /dev/null -w "%{http_code}\n" http://127.0.0.1/curl -H "Host: example.com" ... - Лог ошибок остаётся пустым. Обрежьте его перед тестом командой
truncate -s 0 /var/log/nginx/error.log, вызовите несколько запросов, посмотрите в него ещё раз. Пустой файл и есть настоящее доказательство. - FPM отвечает на сокете мимо nginx, через
cgi-fcgiи от имениwww-data. Тем самым вопрос прав и вопрос пути закрываются за один шаг. - Перезагрузка ничего не меняет. Самый важный и чаще всего пропускаемый пункт. Многие срочные меры (выставленные вручную права, созданные вручную каталоги в
/run, запущенная, но не включённая в автозапуск служба) перезагрузку не переживают. Проверьтеsystemctl is-enabled php8.2-fpm nginxи перезагрузите сервер один раз контролируемо, пока вы ещё за ним смотрите, вместо того чтобы оставлять это следующему окну обслуживания.
На KVM root-серверах и выделенных серверах KernelHost эту перезагрузку вместе с доступом к консоли вы выполняете в личном кабинете, даже если веб-служба сейчас недоступна. Серверы стоят в дата-центре maincubes во Франкфурте-на-Майне (TÜV TIER3+), подключённом к собственной сети с защитой от DDoS. Дополнительно по теме: устранение ошибки nginx 504 Gateway Time-out и правильный расчёт pm.max_children в PHP-FPM.
Краткий чек-лист для критической ситуации
tail -f /var/log/nginx/error.log, вызвать запрос, записать номер ошибки.- Номер 2 или 111: работает ли служба, верен ли путь.
ss -lx | grep phpпротивgrep -Rn "fastcgi_pass" /etc/nginx/. - Номер 13: права в файле пула, а не через
chmod. - Номер 104 или 110: читать лог FPM и
slowlog, и только после этого говорить о сроках. - Сообщение «too big header»: увеличить размеры буферов.
- После исправления: обрезать лог, протестировать заново, один раз перезагрузить сервер.
Частые вопросы
Почему я вижу 502, а не 504, хотя страница просто медленная?
После обновления до Debian 13 все PHP-страницы отдают 502. Что нужно изменить?
Достаточно ли «nginx -t» как доказательства, что ошибка устранена?
Как найти в логе ошибок ту строку, которая относится к моей ошибке 502?
Ошибка 502 возникает только у вошедших в систему пользователей, стартовая страница грузится нормально. С чем это связано?
Я исправил права на сокет через chmod, после перезагрузки ошибка вернулась. Почему?
2026 KernelHost GmbH. Все права защищены. Эта инструкция охраняется авторским правом. Публикация на других сайтах, в том числе частично или в изменённом виде, без нашего письменного согласия не разрешается. Цитирование с указанием источника и активной ссылкой мы приветствуем.

