Обратный прокси Nginx и диагностика 502/504¶
502 Bad Gateway или 504 Gateway Timeout от nginx означает, что сам nginx работает нормально — он успешно получил запрос и сообщает вам, что бэкенд, к которому он проксирует, не ответил корректно (502) или не ответил вовремя (504). Это один из самых частых реальных продакшн- инцидентов, и почти всегда диагностируется меньше чем за пять минут, если проверять в правильном порядке.
Как это устроено¶
Минимальный конфиг обратного прокси¶
# /etc/nginx/sites-available/app.conf
upstream app_backend {
server 127.0.0.1:8000;
}
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://app_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 60s;
}
}
sudo ln -s /etc/nginx/sites-available/app.conf /etc/nginx/sites-enabled/
sudo nginx -t # проверка синтаксиса ПЕРЕД reload — тот же принцип, что у sshd -t
sudo systemctl reload nginx # reload, не restart — применение конфига без простоя
proxy_set_header X-Forwarded-For важен для всего нижестоящего, что нуждается в реальном IP клиента (логи доступа, rate limiting, geo-IP) — без него бэкенд всегда видит в качестве источника запроса только сам IP nginx.
Диагностика 502 Bad Gateway¶
502 означает, что nginx вообще не смог получить валидный ответ от бэкенда — обычно потому, что процесс бэкенда не запущен, не слушает ожидаемый порт, или резко оборвал соединение.
# 1. Вообще что-то слушает там, где ожидает nginx?
ss -tlnp | grep 8000
# 2. Посмотреть собственный лог ошибок nginx на предмет конкретной причины
sudo tail -50 /var/log/nginx/error.log
connect() failed (111: Connection refused) в логе ошибок означает, что на этом порту вообще никто не слушает — процесс бэкенда упал, не запущен, или слушает другой порт/интерфейс, чем настроено. (104: Connection reset by peer) означает, что что-то слушало, но резко оборвало соединение посреди запроса — часто само приложение падает именно на этом запросе (проверяйте дальше логи приложения, не nginx).
Диагностика 504 Gateway Timeout¶
504 означает, что nginx действительно подключился к бэкенду, но не получил ответ в пределах proxy_read_timeout (по умолчанию 60с). Это указывает, что бэкенд жив, но слишком медленный, а не отсутствует.
# Бэкенд медленный или вообще завис?
time curl -sS http://127.0.0.1:8000/health # вызвать его напрямую, минуя nginx
Если прямой curl тоже зависает дольше таймаута, проблема точно в самом приложении бэкенда (проверяйте его собственные логи, исчерпание пула потоков/воркеров, медленный нижестоящий запрос к БД, которого оно ждёт). Если прямой вызов возвращается быстро, а nginx всё равно отваливается по таймауту, подозревайте само значение proxy_read_timeout — слишком низкое для легитимно медленного эндпоинта, либо проблему именно на хопе nginx → бэкенд.
Основы балансировки нагрузки¶
upstream app_backend {
least_conn;
server 10.0.0.11:8000;
server 10.0.0.12:8000;
server 10.0.0.13:8000 backup;
}
least_conn отправляет новые соединения на тот бэкенд, у которого сейчас меньше всего активных соединений (в отличие от round-robin по умолчанию, который вообще игнорирует нагрузку бэкенда). backup помечает сервер как используемый только тогда, когда все не-backup серверы недоступны — полезно для резервного инстанса.
Различия Ubuntu/Debian и RHEL-подобных систем¶
| Ubuntu/Debian | RHEL-подобные | |
|---|---|---|
| Расположение конфигов | /etc/nginx/sites-available/ + симлинки в sites-enabled/ | /etc/nginx/conf.d/*.conf (без разделения enabled/available по соглашению) |
| SELinux | Н/Д | Обратный прокси на нестандартный порт может потребовать setsebool httpd_can_network_connect on, иначе SELinux заблокирует прокси-соединение независимо от правил файрвола |
Практическое задание¶
- Запустите тривиальный бэкенд:
python3 -m http.server 8000. - Настройте nginx на проксирование к нему (как в примере выше) и убедитесь, что работает через
curl http://localhost/. - Остановите Python-сервер (Ctrl+C) и убедитесь, что nginx теперь возвращает
502; проверьтеerror.logна точную причину. - Перезапустите бэкенд, но сделайте его искусственно медленным (например,
python3 -c "import time; time.sleep(120)"за кастомным хендлером, или просто временно выставьтеproxy_read_timeout 2s;) и убедитесь в504.
Как проверить, что всё сделано правильно¶
- Шаг 3: HTTP-ответ должен быть ровно
502, а новая строка вerror.log— упоминатьConnection refusedсо ссылкой на адрес и порт бэкенда. - Шаг 4: ответ должен быть
504после ожидания настроенногоproxy_read_timeout, не раньше.
Типичные ошибки¶
- Перезапуск nginx вместо reload для изменения конфига.
reloadперечитывает конфиг без потери ни одного соединения;restartненадолго полностью закрывает слушающий сокет. - Увеличение
proxy_read_timeoutкак первая реакция на 504, без проверки, не завис ли бэкенд на самом деле (бесконечный цикл, заблокированный воркер), а не просто легитимно медленный — более долгий таймаут на реально зависшем бэкенде просто заставляет клиентов дольше ждать того же сбоя. - Не проверять собственные логи приложения, только логи nginx — лог ошибок nginx говорит что бэкенд не ответил, редко почему сам бэкенд упал.
⚠ Опасные команды¶
systemctl restart nginxна продакшн-хосте с активными долгоживущими соединениями (WebSockets, long-polling) немедленно обрывает их все —reloadпочти всегда правильный выбор для изменений конфига;restartоставьте на случай, когда сам nginx реально завис.- Тестирование нового
proxy_passupstream в продакшне безnginx -tсначала может положить весь сайт целиком из-за опечатки, а не только изменяемый маршрут (синтаксическая ошибка в одном блокеserverможет помешать nginx перезагрузиться вообще, оставив работать предыдущий конфиг до исправления — это на самом деле защитная особенностьreload, но только если никогда не обходить-tпринудительным убийством и перезапуском).
Связанные статьи¶
Контрольные вопросы¶
- В чём практическая разница между тем, что сообщают 502 и 504 о месте сбоя?
- Почему
nginx -tвсегда нужно запускать передreload, даже хотя самreloadописывается как «безопасный»? - Почему увеличение
proxy_read_timeoutне исправляет бэкенд, который реально завис в дедлоке, хотя «исправляет» симптом в виде 504? - В чём операционная разница между
systemctl reload nginxиsystemctl restart nginxдля уже подключённых клиентов?