Динамическое конфигурирование: как перезагрузить Nginx без потери соединений
Поменяли конфиг Nginx, выполнили nginx -s reload — и в логах посыпались ошибки от клиентов. WebSocket-сессии упали, загрузка больших файлов прервалась, а пользователи обновляют страницы. Вроде бы сделали reload, а не restart, но часть соединений всё равно потерялась.
Почему так происходит и как сделать перезагрузку по-настоящему бесшовной?
Давайте разбираться.
💡 Как работает Graceful Reload
Когда вы отправляете сигнал nginx -s reload, главный процесс (Master) получает сигнал SIGHUP и запускает целую цепочку событий:
1️⃣ Проверка: Master-процесс проверяет синтаксис новой конфигурации. Если там ошибка, релоад отменяется, а Nginx продолжает работать на старом конфиге (это встроенная защита).
2️⃣ Запуск новых воркеров: Если всё ок, Master запускает новые рабочие процессы (Workers) с обновленной конфигурацией.
3️⃣ Мгновенный подхват трафика: Новые воркеры сразу начинают принимать новые запросы. Им не нужно заново занимать порты — слушающие сокеты всегда удерживает Master-процесс.
4️⃣ Увядание старых воркеров: Старые процессы получают сигнал на закрытие. Они перестают принимать новые соединения (выходят из цикла `accept`) и занимаются только обслуживанием уже открытых сессий.
❓ Почему рвутся соединения
Если схема идеальна, откуда ошибки? Причин обычно две:
🔹 Директива worker_shutdown_timeout: По умолчанию старые воркеры ждут завершения всех своих соединений бесконечно. Но во многих конфигах (или дефолтных чартах Kubernetes) выставляют этот таймаут (например, 5 минут). Как только время истекает, старый воркер принудительно завершается, убивая все живые WebSocket-сессии и недокачанные файлы.
🔹 Специфика HTTP/2 и HTTP/3: При релоаде Nginx отправляет клиентам фрейм GOAWAY. Это вежливое «переподключитесь, пожалуйста». Большинство современных браузеров делают это незаметно, но самописные клиенты или старые библиотеки могут выдать ошибку соединения.
😎 Правильный пайплайн обновления конфигурации
Чтобы минимизировать риски в продакшене, автоматизация (CI/CD, Ansible, Bash) должна следовать строгому алгоритму.
1️⃣ Атомарная проверка и перезагрузка
Никогда не делайте reload вслепую. Сначала — валидация.
Пример безопасного Bash-скрипта для продакшена:
#!/bin/bash
set -e
# Проверяем синтаксис конфигурации
if nginx -t > /dev/null 2>&1; then
echo " Настройка корректна. Перезапускаем воркеры..."
nginx -s reload
echo " Релоад успешно выполнен."
else
echo " Ошибка в конфиге! Отмена операции."
nginx -t # Выводим ошибку в консоль для логирования
exit 1
fi
2️⃣ Особенности работы в Docker и Kubernetes
В контейнерах Nginx обычно работает как PID 1. Перезапускать сам контейнер ради смены конфига — плохая идея (это гарантированный даунтайн).
Вместо этого отправляйте сигнал прямо в контейнер:
docker kill -s HUP <container_name_or_id>
Совет: если вы используете динамическую генерацию конфигов (например, через consul-template или envsubst`), обязательно прогоняйте `docker exec <container> nginx -t перед тем, как слать сигнал HUP.
Настройка баланса: `worker_shutdown_timeout`
Внесите эту директиву в главный блок nginx.conf (на уровне main, рядом с `worker_processes`), чтобы контролировать жизненный цикл старых процессов:
worker_processes auto;
worker_shutdown_timeout 15m; # Даем старым воркерам 15 минут на завершение долгих скачиваний
🔹 Если у вас много WebSocket/EventSource соединений, ставьте таймаут больше или не ставьте вовсе (но следите за потреблением памяти старыми процессами).
🔹 Если у вас обычный REST API — достаточно 10–30 секунд, чтобы «долить» долгие запросы.