Траблшутинг: 502, 504, 403, 404 и debug-лог

Рейтинг: 59.6% · 10 голосов
Самый подробный курс по Nginx - от первого конфига до production API-gateway. Архитектура и event loop, контексты и location, виртуальные хосты, статика, reverse proxy и upstream-балансировка, кэширование, HTTPS и TLS (post-quantum), HTTP/2 и HTTP/3 QUIC, безопасность и rate limiting, realip, JSON-логи и сквозная трассировка, метрики, тюнинг и траблшутинг. Плюс OpenResty (Lua, фазы, JWT, динамика) и Angie (нативные Prometheus, ACME, dynamic upstream). На основе эталонного gateway и лучших практик. Для новичков и SRE/DevOps. Актуально на 2026 (nginx 1.30/1.31, Angie, ingress-nginx EOL -> Gateway API).
Ответить
Аватара пользователя
Igor_NgINX
Сообщения: 44
Зарегистрирован: 11 май 2026, 05:31

Траблшутинг: 502, 504, 403, 404 и debug-лог

Сообщение Igor_NgINX »

Оглавление курса (44)
  1. Что такое Nginx и почему он захватил веб
  2. Установка Nginx, Angie и OpenResty: пакеты, версии, Docker
  3. Первый конфиг с нуля: минимальный сервер за 5 минут
  4. Архитектура процессов: master, worker и event loop
  5. Анатомия конфигурации: контексты, наследование и модульность
  6. Виртуальные хосты: server и server_name
  7. Директива location: матчинг и приоритеты по шагам
  8. Раздача статики: root, alias, try_files, sendfile
  9. Переменные, map и блок if
  10. rewrite, return и канонизация URL
  11. Работа с заголовками: add_header и proxy_set_header
  12. Модель доверия прокси-заголовков: realip и защита от спуфинга
  13. Сжатие: gzip, brotli и zstd
  14. Reverse proxy: proxy_pass и проброс запроса
  15. upstream и балансировка нагрузки
  16. Таймауты, повторы и устойчивость прокси
  17. FastCGI и PHP: nginx + PHP-FPM (и uwsgi/scgi)
  18. WebSocket, gRPC и стриминг через Nginx
  19. Кэширование ответов: proxy_cache и микрокэш
  20. HTTPS и TLS: сертификаты, протоколы, шифры (2026)
  21. Let's Encrypt и ACME: certbot, acme.sh, нативный ACME Angie
  22. HTTP/2 и HTTP/3 (QUIC) в 2026
  23. Безопасность: заголовки, скрытие версии, ограничения
  24. Rate limiting и защита от перегрузки
  25. Контроль доступа: allow/deny, auth_basic, auth_request и mTLS
  26. Логирование: access_log, форматы и структурированный JSON
  27. Сквозная трассировка запросов и OpenTelemetry
  28. Метрики и мониторинг Nginx
  29. Производительность и тюнинг под нагрузку
  30. Траблшутинг: 502, 504, 403, 404 и debug-лог (вы здесь)
  31. OpenResty: Nginx как платформа на LuaJIT
  32. Фазы обработки запроса и Lua-хуки
  33. Lua API: ngx.*, shared dict, cosocket и lua-resty-core
  34. Практика OpenResty: авторизация, JWT, кэш, Redis
  35. Angie: современный форк Nginx 2026
  36. Динамическая конфигурация и service discovery
  37. Nginx как API-gateway и Kubernetes Gateway API
  38. Динамические модули, njs и WAF
  39. Stream-модуль: проксирование TCP и UDP
  40. Деплой, перезагрузка и конфиг как код
  41. Сценарий: статика, SPA и кэширование за CDN
  42. Сценарий: микросервисный gateway по образцу ngx-trace-gateway
  43. Капстоун: собираем production-gateway с нуля
  44. Чек-лист безопасности, CVE 2026 и аудит конфигурации
Когда сломалось: первый вопрос - кто отдал ошибку

Три часа ночи, в чате паника, мониторинг красный, а пользователи видят белую страницу с надписью "502 Bad Gateway". Знакомо? Самая частая ошибка новичка в этот момент - начать крутить рандомные директивы и перезапускать nginx, надеясь, что само рассосётся. Не рассосётся. nginx troubleshooting - это не угадайка, а холодный детектив по логам.

Первое, что надо понять про любую ошибку шлюза: где она родилась. nginx стоит посередине - между клиентом и бэкендом. Ошибку может вернуть сам nginx (не дошло до бэкенда, права, лимиты), а может проксировать ответ бэкенда насквозь. Это два совершенно разных мира лечения, и путать их - терять часы.

Грубое правило, которое экономит время:
  • 502 / 504 - почти всегда nginx не смог нормально поговорить с апстримом. Смотри бэкенд и сеть между ними.
  • 403 / 404 / 413 - чаще всего сам nginx отказал ещё до апстрима: права, конфиг location, лимиты тела.
  • 400 / 421 / 426 - проблема на уровне самого протокола/запроса, до маршрутизации.
Но "грубое правило" - это гипотеза, а не диагноз. Диагноз живёт в error_log. Поэтому весь этот урок - про то, как читать лог и быстро идти от симптома к причине и лечению. Если nginx не работает или nginx выдаёт ошибку, ответ почти всегда уже записан в файл, ты просто его ещё не открыл.

Изображение

Чтение error_log: одна строка - вся история

access_log говорит что вернули клиенту. error_log говорит почему. При траблшутинге твой главный файл - именно error_log. Открой реальную строку 502:

Код: Выделить всё

2026/06/17 03:14:22 [error] 1187#1187: *4521 connect() failed
(111: Connection refused) while connecting to upstream,
client: 203.0.113.7, server: api.example.com,
request: "GET /v1/orders HTTP/1.1",
upstream: "http://127.0.0.1:9000/v1/orders",
host: "api.example.com"
Разбери её по полям, это шаблон для всех ошибок nginx:
  • [error] - уровень. Бывает debug, info, notice, warn, error, crit, alert, emerg.
  • *4521 - номер соединения. По нему можно grep-нуть всю жизнь запроса в debug-логе.
  • connect() failed (111: Connection refused) - ВОТ причина. Системный вызов connect() к апстриму вернул errno 111. Порт никто не слушает.
  • while connecting to upstream - стадия. nginx ещё только устанавливал соединение.
  • upstream: "http://127.0.0.1:9000/..." - КУДА он стучался. Сверь с тем, где реально слушает бэкенд.
Связка "что за syscall + какой errno + на какой стадии" - это 80 процентов диагноза. Запомни типовые формулировки:
  • connect() failed (111: Connection refused) - бэкенд не слушает этот адрес/порт. Лежит или слушает другое.
  • upstream timed out (110: Connection timed out) while reading response header - бэкенд жив, но думает дольше, чем proxy_read_timeout. Это дорога к 504.
  • connect() to unix:/run/php-fpm.sock failed (13: Permission denied) - права на сокет или SELinux.
  • no live upstreams while connecting to upstream - все серверы апстрима помечены недоступными после max_fails.
  • open() "/var/www/html/img/x.png" failed (13: Permission denied) - 403, права на файл/каталог.
  • open() "..." failed (2: No such file or directory) - 404, неверный root/alias/try_files.
Заметь: errno 13 (Permission denied) и errno 2 (No such file) - твои постоянные спутники в 403/404. Они системные, не nginx-овские, и гуглятся как errno.

Уровни логов и debug-лог: когда обычного error_log мало

По умолчанию error_log пишет с уровня error (плюс всё, что серьёзнее). Этого хватает для большинства аварий. Но иногда причина тонкая - например, "почему вообще выбрался этот location" или "почему отвалилось keepalive-соединение к апстриму". Тогда нужен debug-лог - он печатает каждый шаг обработки: разбор запроса, выбор location, фазы rewrite, открытие файлов, диалог с апстримом.

Включается так:

Код: Выделить всё

error_log /var/log/nginx/error.log debug;
Критичный нюанс: debug работает только если nginx собран с поддержкой отладки (флаг сборки --with-debug). Иначе строка с уровнем debug просто проигнорируется, и ты будешь смотреть в пустоту. Проверь:

Код: Выделить всё

nginx -V 2>&1 | tr ' ' '\n' | grep -- --with-debug
Если пусто - в официальных пакетах nginx.org обычно есть отдельный бинарь nginx-debug либо отдельный пакет с отладочной сборкой. В Angie и в стандартных docker-образах отладка чаще включена из коробки.

debug-лог огромный, поэтому не включай его глобально на проде. Сужай по клиенту - это спасает диск и нервы:

Код: Выделить всё

events {
    debug_connection 203.0.113.7;
    debug_connection 10.0.0.0/8;
}
Теперь полный debug пишется только для соединений с этих адресов, а остальной трафик - в обычном режиме. Идеально для воспроизведения бага с одного тестового IP на живом сервере. И помни про директиву из эталона global_logging.conf: structured_log с escape=json даёт машинно-читаемые поля (upstream_response_time, upstream_status, request_time), по которым 504 и медленные апстримы видно без regexp по тексту. Кстати, в свежих nginx у самой error_log появился параметр json - error_log в JSON упрощает разбор аварий в ELK/Loki.

Перед тем как лезть в debug, всегда прогони два базовых заклинания:

Код: Выделить всё

nginx -t       # синтаксис + базовая валидация конфига
nginx -T       # то же + дамп ВСЕГО итогового конфига со всеми include
nginx -t ловит опечатки и несбалансированные скобки. А nginx -T бесценен, когда конфиг разнесён по 14 файлам globals/* и sites-enabled/* (как в эталонном ngx-trace-gateway): он показывает финальную склейку, и ты сразу видишь, какой add_header реально наследуется и какой server_name победил. Половина "мистических" багов - это не то, что ты думал, что лежит в конфиге.

Систематика по кодам: компендиум симптом -> причина -> лечение

Теперь главное - разбор кодов как FAQ. Держи это под рукой как чек-лист.

nginx 502 bad gateway - "бэкенд ответил мусором или не ответил вовсе".
nginx установил (или попытался) соединение, но не получил валидный HTTP-ответ. Причины по частоте:
  • Бэкенд лёг/упал/не запущен. Лог: connect() failed (111: Connection refused). Лечение: проверь, что процесс жив и слушает (ss -ltnp, systemctl status).
  • Неверный proxy_pass - стучимся не туда (не тот порт/хост/сокет). Сверь upstream: в логе с реальностью.
  • php-fpm слушает по unix-сокету, а в конфиге путь/права не те. Лог: connect() to unix:/run/php-fpm.sock failed. Лечение: выровнять listen в pool php-fpm и fastcgi_pass; проверить владельца сокета.
  • SELinux на RHEL/Rocky/Alma блокирует исходящее соединение nginx к апстриму. Симптом коварный: процесс жив, порт слушает, а 502 есть. Лог в audit.log. Лечение: setsebool -P httpd_can_network_connect 1 (или httpd_can_network_relay для проксирования). Это реально частая, легко упускаемая причина.
  • Бэкенд вернул битый/слишком большой заголовок - не влез в proxy_buffer_size. Лог: upstream sent too big header. Лечение: поднять proxy_buffer_size / proxy_buffers (в эталоне 16k и 16x16k).
nginx 504 gateway timeout - "бэкенд жив, но слишком медленный".
Соединение установилось, запрос ушёл, а ответ не пришёл за отведённое время. Лог-маркер: upstream timed out (110: Connection timed out) while reading response header. Лечение:
  • Найди, что тормозит: тяжёлый SQL, внешний API, блокировка. Чини на стороне приложения - это первично.
  • Если ответ объективно долгий (отчёт, выгрузка) - подними таймауты точечно для этого location, а не глобально: proxy_read_timeout 120s; (по умолчанию и в эталоне - 10s). Глобально задирать опасно: повиснет один зависший бэкенд - и воркеры будут держать соединения дольше.
  • Помни про связку с php-fpm: если request_terminate_timeout в php-fpm меньше proxy_read_timeout, 504 даст fastcgi, и таймаут надо поднимать в двух местах.
  • proxy_connect_timeout (в эталоне 5s) - это таймаут на сам connect(), а proxy_read/send_timeout - на чтение/отправку. Не путай: connect-таймаут срабатывает, когда сеть не пускает, read-таймаут - когда бэкенд завис уже после соединения.
nginx 403 forbidden - "доступ запрещён".
Чаще nginx решает это сам, не доходя до апстрима. Причины:
  • Права файла/каталога: nginx-воркер (от пользователя nginx/www-data) не может прочитать файл. Лог: open() ... failed (13: Permission denied). Лечение: chmod/chown; путь к файлу должен быть исполняемым (x) на всех каталогах выше.
  • Запрос каталога при autoindex off и без index-файла. Лог: directory index of "..." is forbidden. Лечение: положить index.html или включить autoindex on (осознанно).
  • Явный deny all / deny по IP в конфиге сработал. Проверь блоки allow/deny - в эталоне ими закрыты dotfiles, .git, config-файлы.
  • SELinux: контекст файла не httpd_sys_content_t. Лечение: restorecon -Rv /var/www.
nginx 404 not found - "файл/маршрут не найден".
Самый частый источник - кривой root/alias/try_files:
  • Перепутаны root и alias. Классическая грабля - alias без завершающего слеша (разберём ниже отдельно). Лог: open() "/неожиданный/путь" failed (2). Смотри в лог - там точный путь, который nginx собрал. 90 процентов 404 решаются сравнением "что в логе" против "что на диске".
  • try_files $uri $uri/ =404 - последний аргумент вернул 404, потому что ни файл, ни каталог, ни fallback не нашлись.
  • Запрос перехватил не тот location (см. порядок location ниже).
413 Request Entity Too Large - "тело запроса больше лимита".
nginx режет запрос ДО передачи на бэкенд. Лог: client intended to send too large body. Лечение: client_max_body_size 50m; (по умолчанию 1m). И не забудь поднять upload_max_filesize/post_max_size в php - иначе nginx пропустит, а php отвергнет.

400 / 421 / 426 - уровень протокола.
  • 400 Bad Request - битый запрос, слишком длинные/кривые заголовки, мусор в строке запроса. В nginx 1.29.8 появилась директива max_headers для защиты от флуда заголовками - перебор лимита тоже даст 400-семейство.
  • 421 Misdirected Request - клиент переиспользовал HTTP/2-соединение для хоста, чей сертификат/SNI не совпадает с этим server-блоком. Лечится корректной раскладкой сертификатов по server_name или отключением переиспользования.
  • 426 Upgrade Required - сервер требует апгрейда протокола (например, заворачивает голый HTTP на HTTPS-обязательный эндпоинт).
ТОП граблей, которые маскируются под "магию"

Эти баги выглядят как порча, но у всех есть скучное объяснение. Они - источник большинства тикетов в nginx troubleshooting.

1. Наследование add_header. Директива add_header не складывается по уровням: если в location есть хоть один add_header, ВСЕ add_header из server/http в этом location исчезают. Поставил в server заголовки безопасности, а в location /api/ добавил один свой - и security-заголовки пропали. Лечение: дублируй нужные заголовки в дочернем блоке либо собирай их в одном месте. Флаг always заставляет слать заголовок даже на 4xx/5xx (в эталоне global_security.conf все заголовки с always - именно поэтому).

2. Порядок и приоритет location. nginx выбирает location НЕ сверху вниз. Сначала проверяются точные (=) и префиксные с ^~, запоминается самый длинный префикс, ПОТОМ regex (~ и ~*) в порядке записи - первый совпавший побеждает. Классика: твой location ~* \.(css|js)$ для статики перехватывает запрос раньше, чем префиксный /api/, и API отдаёт 404. Лечение: критичные префиксы закрывай через ^~ (как location ^~ /api/ в эталоне), чтобы regex до них не дотянулся.

3. alias и завершающий слеш. Самая болезненная 404-грабля:

Код: Выделить всё

# СЛОМАНО: слеши не согласованы
location /static/ {
    alias /var/www/assets;     # нет слеша на конце
}
# запрос /static/app.js -> nginx ищет /var/www/assetsapp.js -> 404

Код: Выделить всё

# ПРАВИЛЬНО: слеш есть в location И в alias
location /static/ {
    alias /var/www/assets/;
}
# запрос /static/app.js -> /var/www/assets/app.js
Правило: при alias следи, чтобы наличие/отсутствие завершающего слеша совпадало с location. И в логе всегда виден итоговый путь - сверяй его.

4. proxy_buffering ломает стриминг. По умолчанию proxy_buffering on - nginx буферизует ответ бэкенда. Для SSE, long-polling, стриминговых API это смерть: клиент не видит данные, пока буфер не заполнится, симптом - "висит и ничего не приходит". Лечение точечно для стрим-локаций:

Код: Выделить всё

location /events {
    proxy_pass http://backend;
    proxy_buffering off;
    proxy_cache off;
    proxy_read_timeout 1h;
}
5. $http_authorization в ключе кэша. В эталонном global_proxy.conf ключ кэша - "$scheme$request_method$host$request_uri$http_authorization". Включение заголовка авторизации в ключ - осознанная защита: без него ответ для авторизованного юзера A может уехать юзеру B с тем же URL. Грабля наоборот: забыл про авторизацию в ключе - получил утечку приватных данных через кэш. Симптом коварен - "иногда вижу чужие данные". Думай, что входит в ключ.

6. realip не настроен. Без модуля realip переменная $remote_addr - это адрес ближайшего прокси/CDN, а не клиента. Последствия: в логах один IP на всех, rate-limit по IP не работает (все под одной зоной), geo и auth по IP врут. Лечение - доверять X-Forwarded-For ТОЛЬКО от известных сетей:

Код: Выделить всё

set_real_ip_from 10.0.0.0/8;      # только твой балансировщик/CDN
real_ip_header X-Forwarded-For;
real_ip_recursive on;
Опасность: если в set_real_ip_from указать 0.0.0.0/0, любой клиент подделает свой IP заголовком X-Forwarded-For и обойдёт бан/лимит. Доверяй только тому, что реально твоё.

7. Свежие CVE как причина "странностей". В 2026 держи nginx обновлённым: CVE-2026-42945 (heap overflow в rewrite-модуле, эксплуатируется с мая 2026) - повод минимизировать rewrite-правила; CVE-2026-40460 - спуфинг адреса при миграции HTTP/3-соединения. Если поведение странное и версия старая - сначала обнови, потом дебажь.

Алгоритм диагностики: воспроизводимая последовательность

Когда прилетело "nginx не работает", не импровизируй - иди по шагам:
  • 1. Какой код у клиента? curl -i, посмотри статус и заголовки. Это задаёт ветку.
  • 2. nginx -t - конфиг вообще валиден? Если нет - чини синтаксис, дальше не иди.
  • 3. Открой error_log, найди свежую строку. Прочти syscall + errno + стадию.
  • 4. 502/504? -> проверь бэкенд: жив ли (ss -ltnp), доступен ли (curl на upstream напрямую), что в его логах. Сверь upstream: в error_log с реальным адресом.
  • 5. 403/404? -> возьми ИЗ ЛОГА точный путь open()... и сравни с диском (ls -la, namei -l). Проверь права, root/alias, try_files.
  • 6. Не видно причины? -> debug_connection на свой тестовый IP, error_log debug, воспроизведи запрос, grep по номеру соединения *NNNN.
  • 7. Исправил - nginx -t, затем nginx -s reload, повтори запрос, подтверди по логу.
Ключевая дисциплина: меняй ОДНО за раз и сверяйся с логом. Шотган-дебаг (поменял пять директив, перезапустил) скрывает, что именно помогло, и плодит новые баги.

Мини-лаба: воспроизведи 502, 504 и 404 руками

Сделай это на тестовой машине или в контейнере openresty/openresty. Цель - увидеть каждую ошибку в логе своими глазами.
  • Шаг 1. Подними nginx с location, проксирующим на заведомо мёртвый порт:

Код: Выделить всё

server {
    listen 8081;
    error_log /tmp/lab_error.log debug;
    location / {
        proxy_pass http://127.0.0.1:9999;  # тут никто не слушает
        proxy_connect_timeout 2s;
    }
}
  • Шаг 2. nginx -t, затем reload. Сделай curl -i http://127.0.0.1:8081/ - получишь 502. Открой /tmp/lab_error.log, найди connect() failed (111: Connection refused). Это твой эталонный 502.
  • Шаг 3. Запусти медленный бэкенд: в одном терминале while true; do printf 'HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\n'; sleep 30; done | nc -l 9999 (или любой скрипт со sleep). Поменяй proxy_pass на порт 9999, добавь proxy_read_timeout 3s; reload.
  • Шаг 4. curl -i снова - через 3 секунды получишь 504. В логе ищи upstream timed out (110) while reading response header. Сравни формулировку с 502 из шага 2 - стадия другая (reading, а не connecting).
  • Шаг 5. Сделай location / с root /tmp/nowhere; и try_files $uri =404;. Запроси /test.txt - получишь 404. В логе будет open() "/tmp/nowhere/test.txt" failed (2: No such file or directory). Создай файл - и тот же запрос отдаст 200. Ты только что прошёл путь "симптом -> точный путь из лога -> лечение".
  • Шаг 6. Сломай alias: location /s/ { alias /tmp/assets; } (без слеша), положи /tmp/assets/x.txt, запроси /s/x.txt. Поймай 404, прочти в логе склеенный путь /tmp/assetsx.txt, добавь слеш в alias, проверь, что починилось.
После лабы у тебя в голове будет якорь: каждая ошибка - это конкретная строка с конкретным syscall, а не абстрактное "сломалось".

Контрольные вопросы
  • 1. В error_log строка: upstream timed out (110) while reading response header. Какой код увидит клиент и в каком ОДНОМ месте ты сначала будешь искать причину - в nginx или в бэкенде?
  • 2. Ты добавил add_header X-Cache в location /api/, и внезапно из этого location пропали заголовки безопасности, заданные в server. Почему и как починить?
  • 3. Запрос /static/app.js даёт 404, в логе open() "/var/www/assetsapp.js" failed (2). Где ошибка в конфиге location/alias?
  • 4. Ты включил error_log ... debug, а debug-строк в логе нет вообще. Какие две причины проверишь первыми?
Итог

Траблшутinга "по наитию" не существует - есть чтение error_log. Запомни главное: код ответа задаёт ветку (502/504 - смотри апстрим, 403/404/413 - смотри сам nginx и файлы), а точная строка лога с syscall, errno и стадией обработки выдаёт причину почти всегда. Включай debug-лог точечно через debug_connection, проверяй итоговый конфиг через nginx -T, держи под рукой компендиум симптом -> причина -> лечение и помни про грабли наследования add_header, порядок location, alias-слеш, buffering и realip. Меняй по одной директиве и сверяйся с логом - тогда любая "nginx ошибка" из паники превращается в пятиминутную процедуру.
👍6 ❤️2 🔥2 😄 🤔
Аватара пользователя
marbler
Сообщения: 1
Зарегистрирован: 13 май 2026, 10:59

Re: Траблшутинг: 502, 504, 403, 404 и debug-лог

Сообщение marbler »

Шаг 5 в лабе прям щёлкнул в голове - я раньше тупо менял root наугад, а оказывается путь же прямо в логе лежит, бери и сравнивай с диском. Спасибо!
👍1 ❤️ 🔥1 😄 🤔1
Аватара пользователя
lfb1127
Сообщения: 1
Зарегистрирован: 15 май 2026, 02:15

Re: Траблшутинг: 502, 504, 403, 404 и debug-лог

Сообщение lfb1127 »

Про add_header не знал что он не наследуется а целиком перетирает родительские. Полдня ловил пропавший HSTS на /api/, теперь понятно почему. Добавил always и продублировал заголовки в location.
👍1 ❤️1 🔥 😄 🤔
Ответить
← Предыдущая глава
Производительность и тюнинг под нагрузку
Следующая глава →
OpenResty: Nginx как платформа на LuaJIT

Все главы курса «Nginx профессионально: от первого конфига до API-gateway на OpenResty и Angie»

Поделиться темой: ✈ Telegram VK
Похожие запросы: Ошибки nginx 403, 404, 504: диагностикаОшибка nginx 502 Bad Gateway: причины и решение

Вернуться в «Nginx профессионально: от первого конфига до API-gateway на OpenResty и Angie»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость