Ошибки 502, 504, 403 и 500 в Nginx: причины и как чинить
Что означает каждый код, где искать причину в журнале Nginx, и типовые исправления для бэкендов на PHP-FPM, Node.js и Python. Ubuntu 24.04, Debian 12.

Код ошибки говорит, на каком участке пути запрос сломался. 502 и 504 — проблема между Nginx и приложением за ним, 403 — права и индексные файлы, 500 — ошибка внутри приложения или зацикленная конфигурация. Причина почти всегда уже написана в error.log, поэтому порядок один: узнать код, прочитать журнал, исправить, проверить.
Для экспериментов подойдёт любой VPS с Ubuntu или Debian; на tanaka.host сервер можно взять на день-два, пройти инструкцию и удалить без лишних трат.
Порядок диагностики
Код ответа:
curl -sI https://example.com | head -1
Причина в журнале Nginx (в соседнем окне, пока обновляете страницу):
tail -f /var/log/nginx/error.log
Если у сайта свой error_log в блоке server, найдите его путь:
nginx -T 2>/dev/null | grep -n error_log
Если в строке ошибки есть слово upstream, виновато приложение за Nginx — смотрите его службу и порт:
systemctl status myapp --no-pager
journalctl -u myapp -n 50 --no-pager
ss -tlnp | grep 8098
Любую правку конфигурации Nginx применяйте только так, чтобы опечатка не уронила все сайты:
nginx -t && systemctl reload nginx
502 Bad Gateway
Nginx передал запрос бэкенду и не получил ответа или получил мусор.
Приложение не запущено. В журнале connect() failed (111: Connection refused) while connecting to upstream. Конфигурация вида:
location / {
proxy_pass http://127.0.0.1:8098;
}
а ss -tlnp | grep 8098 пуст. Смотрите journalctl -u myapp — обычно падение из-за битого конфига или занятого порта. Исправьте и запустите, затем проверьте бэкенд напрямую, минуя Nginx:
systemctl start myapp
curl -I http://127.0.0.1:8098
Чтобы приложение поднималось само, добавьте в его unit:
[Service]
# Перезапускаем приложение, если оно упало
Restart=on-failure
# Ждём три секунды перед перезапуском
RestartSec=3
systemctl daemon-reload
systemctl enable myapp
Про unit-файлы подробнее — служба systemd на примере бота.
PHP-FPM: сокет не найден. В журнале connect() to unix:/run/php/php8.1-fpm.sock failed (2: No such file or directory). Это типично после обновления PHP: пакет стал php8.3-fpm, а в конфиге сайта остался старый путь.
ls /run/php/
sed -i 's/php8.1-fpm.sock/php8.3-fpm.sock/' /etc/nginx/sites-available/example.com
nginx -t && systemctl reload nginx
PHP-FPM: нет доступа к сокету. (13: Permission denied) while connecting to upstream. Пул запущен от другого пользователя, и сокет создан с правами, которые не пускают www-data. В файле пула (/etc/php/8.3/fpm/pool.d/<пул>.conf) владелец сокета должен совпадать с пользователем Nginx:
cd /etc/php/8.3/fpm/pool.d
printf 'listen.owner = www-data\nlisten.group = www-data\n' >> errors.conf
php-fpm8.3 -t && systemctl restart php8.3-fpm
ls -l /run/php/errors-fpm.sock
Здесь нужен именно restart, а не reload: сокет пересоздаётся при старте.
Слишком большие заголовки. upstream sent too big header while reading response header from upstream — приложение отдаёт много cookie или длинные заголовки. Увеличьте буферы:
location / {
proxy_pass http://127.0.0.1:8098;
proxy_buffer_size 16k;
proxy_buffers 8 16k;
}
504 Gateway Time-out
Бэкенд жив, но не ответил за proxy_read_timeout (по умолчанию 60 с). В журнале upstream timed out (110: Connection timed out). Замерьте, сколько реально отвечает приложение:
curl -s -o /dev/null -w '%{http_code} через %{time_total} с\n' http://127.0.0.1:8093/slow
Правильное лечение — ускорить запрос (индекс в базе, кеш, вынести работу в очередь). Если запрос по своей природе долгий — экспорт отчёта, генерация — поднимите лимит для этого location:
location / {
proxy_pass http://127.0.0.1:8098;
proxy_read_timeout 120s;
}
Для PHP-FPM директива называется fastcgi_read_timeout. Учтите второй лимит — max_execution_time в php.ini (по умолчанию 30 с): если он сработает раньше, вместо 504 вы получите 500 с Maximum execution time exceeded в журнале PHP.
403 Forbidden
Нет индексного файла. Запрос на /, в папке нет index.html или index.php, а autoindex выключен — Nginx отдаёт 403, в журнале directory index of "/var/www/site/" is forbidden.
ls -la /var/www/site
echo '<h1>Сайт работает</h1>' > /var/www/site/index.html
curl -sI http://127.0.0.1/ | head -1
Если папка должна показывать листинг (раздача файлов), включите autoindex on; в location.
Нет прав на файл. open() "/var/www/site/index.html" failed (13: Permission denied). Узнайте, от кого работает Nginx, и посмотрите права на каждом уровне пути:
grep ^user /etc/nginx/nginx.conf
namei -l /var/www/site/index.html
namei покажет всю цепочку /var, /var/www, /var/www/site, файл — и сразу видно, где права обрезаны. Нормальные права: папки 755, файлы 644, владелец www-data:
chown -R www-data:www-data /var/www/site
find /var/www/site -type d -exec chmod 755 {} +
find /var/www/site -type f -exec chmod 644 {} +
Не ставьте 777. Это открывает запись в файлы сайта любому процессу на сервере, включая взломанный скрипт соседнего сайта. Если после 644/755 ошибка осталась, проблема в папке выше по пути — её покажет
namei -l.
Закрыта папка на пути. Файл 644, но одна из родительских папок без x для остальных (например, 700 на /var/www/site). Nginx не может в неё войти:
namei -l /var/www/site/public/index.html
chmod 755 /var/www/site
500 Internal Server Error
Фатальная ошибка PHP. В журнале PHP Fatal error: Uncaught Error: Call to undefined function mb_strlen() — не хватает модуля. Ставим по имени:
apt install -y php-mbstring
systemctl restart php8.3-fpm
Так же лечатся curl_init (php-curl), simplexml_load_string (php-xml), imagecreate (php-gd), PDO ... could not find driver (php-mysql). Какой модуль нужен, обычно пишет установщик CMS.
Синтаксическая ошибка. PHP Parse error: syntax error, unexpected .... Проверить файл без запуска:
php -l /var/www/site/parse.php
Цикл внутренних перенаправлений. rewrite or internal redirection cycle while internally redirecting to "/index.html". Типичный конфиг для SPA:
location / {
try_files $uri /index.html;
}
Если index.html в root отсутствует, Nginx ищет его, не находит, снова перенаправляет на него — и через 10 итераций сдаётся с 500. Проверьте, что файл есть и root указывает туда, куда вы думаете:
ls -la /var/www/site
echo '<h1>Сайт работает</h1>' > /var/www/site/index.html
curl -sI http://127.0.0.1/ | head -1
Шпаргалка
| Код | Строка в error.log | Первое, что проверить |
|---|---|---|
| 502 | Connection refused |
systemctl status, ss -tlnp |
| 502 | No such file or directory + .sock |
ls /run/php/, версия PHP в конфиге |
| 502 | Permission denied + .sock |
listen.owner/listen.group в пуле |
| 502 | too big header |
proxy_buffer_size |
| 504 | upstream timed out |
время ответа бэкенда, proxy_read_timeout |
| 403 | directory index ... is forbidden |
есть ли index.html |
| 403 | open() ... Permission denied |
namei -l, права 755/644 |
| 500 | PHP Fatal error |
недостающий модуль, php -l |
| 500 | internal redirection cycle |
существует ли файл из try_files |
Чтобы ошибки у каждого сайта не смешивались, задайте отдельный журнал в блоке server:
error_log /var/log/nginx/example.com.log;
Базовая связка Nginx + PHP-FPM с нуля описана в статье Nginx, PHP-FPM и SSL, а чтобы узнавать о 502 раньше клиентов — Uptime Kuma.