Обеспечение отказоустойчивости Nginx: upstream, health checks и keepalive на практике | AdminWiki

Обеспечение отказоустойчивости Nginx: upstream, health checks и keepalive на практике

27 июля 2026 9 мин. чтения

Почему Nginx - критическая точка отказа и как этого избежать

Nginx обрабатывает входящий трафик и направляет его к бэкендам. Если этот единственный балансировщик выходит из строя, пользователи теряют доступ ко всему сервису. По данным опросов администраторов, до 40% незапланированных простоев веб-приложений связаны с отказом балансировщика нагрузки, а не самих серверов приложений.

Решение состоит из трёх компонентов. Группы upstream объединяют несколько серверов и распределяют нагрузку между ними. Health checks автоматически исключают упавшие узлы без вмешательства администратора. Keepalive-соединения снижают накладные расходы на установку TCP-соединений и ускоряют ответ бэкендов. Эта статья даёт готовые конфигурации для каждого компонента и показывает, как собрать их в единую отказоустойчивую схему.

Если вам нужен более широкий взгляд на балансировку, обратитесь к руководству по алгоритмам и стратегиям распределения трафика в Nginx. Там разобраны методы балансировки, веса и резервные серверы для разных типов приложений.

Группировка бэкендов: настройка upstream для балансировки нагрузки

Директива upstream определяет пул серверов, между которыми Nginx распределяет запросы. Это первый шаг к отказоустойчивости: если один бэкенд недоступен, трафик уходит на остальные. Синтаксис прост, но детали определяют поведение при сбоях.

Базовый пример upstream с двумя бэкендами

Минимальная рабочая конфигурация выглядит так:

upstream backend {
    server 192.168.1.10:8080 weight=3;
    server 192.168.1.11:8080 weight=1;
}

server {
    listen 80;
    location / {
        proxy_pass http://backend;
    }
}

Параметр weight задаёт пропорцию распределения запросов. Сервер 192.168.1.10 получит примерно 75% трафика, сервер 192.168.1.11 - 25%. Веса полезны, когда бэкенды имеют разную аппаратную мощность. Без явного указания веса все серверы считаются равнозначными.

Директива server принимает IP-адрес или доменное имя с портом. Порт обязателен, даже если используется стандартный 80. Nginx не подставляет порт по умолчанию для upstream-серверов.

Выбор метода балансировки под вашу задачу

Метод балансировки определяет, какому бэкенду достанется следующий запрос. Выбор зависит от характера приложения.

Round-robin - метод по умолчанию. Запросы распределяются равномерно по кругу. Подходит для stateless-приложений, где каждый запрос независим. Не учитывает текущую загрузку серверов.

upstream backend {
    # round-robin включён по умолчанию
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
}

Least connections (least_conn) - отправляет запрос серверу с наименьшим количеством активных соединений. Полезен, когда запросы сильно различаются по времени обработки. Длинные запросы не блокируют один сервер, пока другие простаивают.

upstream backend {
    least_conn;
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
}

IP hash (ip_hash) - привязывает клиента к конкретному бэкенду по IP-адресу. Все запросы от одного пользователя попадают на один сервер. Критичен для приложений с сессиями на стороне сервера, когда нет общего хранилища сессий.

upstream backend {
    ip_hash;
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
}

Random - выбирает сервер случайным образом. С параметром two выбирает два сервера и отдаёт запрос менее загруженному. Компромисс между равномерностью распределения и учётом загрузки.

upstream backend {
    random two;
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
}

Детальный разбор алгоритмов с примерами для API, WebSocket и stateful-приложений есть в статье по настройке балансировки Nginx за 5 минут.

Автоматическое исключение упавших серверов: health checks в Nginx

Группа upstream распределяет трафик, но не проверяет доступность серверов. Без health checks запросы продолжают уходить на упавший бэкенд, и клиенты получают ошибки 502 Bad Gateway. Проверки здоровья решают эту проблему автоматически.

Пассивные health checks: настройка в open-source Nginx

Бесплатная версия Nginx использует пассивные проверки. Балансировщик наблюдает за ответами бэкендов и помечает сервер как неработающий после заданного количества неудачных попыток. Настройка выполняется параметрами max_fails и fail_timeout внутри директивы server.

upstream backend {
    server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;
    server 192.168.1.11:8080 max_fails=3 fail_timeout=30s;
    server 192.168.1.12:8080 backup;
}

Параметр max_fails=3 указывает: после трёх неудачных попыток связи с бэкендом в течение fail_timeout=30s сервер считается недоступным на 30 секунд. По истечении этого времени Nginx пробует отправить запрос снова. Если бэкенд отвечает успешно, он возвращается в пул.

Сервер с флагом backup получает трафик только тогда, когда все основные бэкенды помечены как неработающие. Это резервный узел, который держат для критических ситуаций.

Пассивные проверки имеют ограничение: Nginx не инициирует тестовые запросы специально. Он узнаёт о проблеме только когда реальный пользовательский трафик попадает на сбойный сервер. Часть запросов всё равно завершится ошибкой до того, как сервер будет исключён.

Активные health checks в Nginx Plus: преимущества и настройка

Коммерческая версия Nginx Plus отправляет тестовые запросы к бэкендам независимо от пользовательского трафика. Это позволяет обнаружить проблему до того, как она затронет клиентов. Настройка выполняется директивой health_check внутри блока location.

upstream backend {
    zone backend 64k;
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
}

server {
    listen 80;
    location / {
        proxy_pass http://backend;
        health_check interval=5 fails=3 passes=2 uri=/health;
    }
}

# Бэкенд должен отвечать на /health
# На стороне приложения добавляется эндпоинт проверки

Директива health_check задаёт интервал проверки (5 секунд), количество последовательных неудач для исключения сервера (3) и количество успешных проверок для возврата в пул (2). URI /health указывает путь, который Nginx запрашивает на каждом бэкенде.

Для проверки не только статуса, но и содержимого ответа используется блок match:

match health_check_response {
    status 200;
    header Content-Type = application/json;
    body ~ "status":"ok";
}

location / {
    proxy_pass http://backend;
    health_check match=health_check_response;
}

Этот блок требует код 200, заголовок Content-Type равный application/json и наличие строки "status":"ok" в теле ответа. Бэкенд, вернувший код 200, но с неверным содержимым, будет исключён из пула.

Подробное руководство по мониторингу состояний пулов и сбору метрик доступно в статье по настройке health checks и визуализации метрик в Grafana.

Keepalive-соединения: снижаем накладные расходы и ускоряем ответ

По умолчанию Nginx использует HTTP/1.0 для соединений с бэкендами и закрывает TCP-соединение после каждого запроса. Установка нового соединения требует трёхстороннего рукопожатия TCP, что добавляет задержку и нагружает ядро операционной системы. При 1000 запросов в секунду это сотни тысяч лишних системных вызовов в минуту.

Keepalive-соединения переиспользуют установленные TCP-соединения для нескольких запросов. Задержка снижается, пропускная способность растёт.

Настройка keepalive в upstream: пошаговый пример

Для включения keepalive нужно изменить три параметра в конфигурации:

upstream backend {
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    keepalive 32;
}

server {
    listen 80;
    location / {
        proxy_pass http://backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }
}

Директива keepalive 32 в блоке upstream указывает максимальное количество простаивающих keepalive-соединений к каждому бэкенду, которые Nginx держит открытыми. Это не лимит на общее количество соединений, а размер пула для переиспользования.

Строка proxy_http_version 1.1 обязательна. HTTP/1.1 включает keepalive по умолчанию, тогда как HTTP/1.0 требует явного заголовка Connection: keep-alive.

Строка proxy_set_header Connection "" очищает заголовок Connection, пришедший от клиента. Если клиент отправил Connection: close, а Nginx пробросит его бэкенду, соединение закроется после первого же запроса и keepalive не сработает.

Мониторинг и тюнинг keepalive-соединений

Эффективность keepalive оценивается через статистику соединений. Модуль stub_status показывает общее количество принятых, обработанных и текущих соединений, но для детального анализа keepalive с бэкендами нужен модуль ngx_http_upstream_module или внешний мониторинг.

Ориентиры для выбора значения keepalive:

  • 32 - достаточное значение для большинства проектов с нагрузкой до 500 запросов в секунду;
  • 64-128 - для высоконагруженных систем с тысячами запросов в секунду;
  • 256 и выше - для кластеров с десятками бэкендов и очень высоким трафиком.

Каждое keepalive-соединение потребляет память ядра на сокет и буферы. При 128 соединениях к 10 бэкендам это 1280 открытых сокетов, что заметно, но некритично для современного сервера. Проблемы начинаются при тысячах keepalive-соединений на сервер - в этом случае лучше увеличить количество бэкендов, а не пул keepalive.

Симптом нехватки keepalive-соединений: в логах бэкендов появляются записи о частом открытии и закрытии соединений, а время ответа растёт под нагрузкой. Симптом избытка: Nginx потребляет неожиданно много памяти, а команда ss -s показывает десятки тысяч TCP-сокетов в состоянии ESTABLISHED.

Для комплексного мониторинга производительности Nginx используйте шпаргалку по пяти ключевым метрикам: Active Connections, Requests per Second, ошибки 4xx/5xx, Upstream Response Time и Waiting Connections.

Комплексный пример: собираем всё вместе для отказоустойчивого кластера

Конфигурация ниже объединяет upstream с двумя бэкендами, пассивные health checks, резервный сервер и keepalive-соединения. Этот файл готов к использованию после замены IP-адресов на реальные.

upstream backend {
    # Метод балансировки - least_conn для неравномерных запросов
    least_conn;
    
    # Основные бэкенды с пассивными health checks
    server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;
    server 192.168.1.11:8080 max_fails=3 fail_timeout=30s;
    
    # Резервный сервер - получает трафик только при отказе основных
    server 192.168.1.12:8080 backup;
    
    # Пул keepalive-соединений
    keepalive 32;
}

server {
    listen 80;
    server_name example.com;
    
    location / {
        proxy_pass http://backend;
        
        # Включаем HTTP/1.1 для keepalive
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        
        # Проксируем заголовки клиента
        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_connect_timeout 5s;
        proxy_read_timeout 60s;
        proxy_send_timeout 60s;
    }
}

Сценарий работы при сбое:

  1. Бэкенд 192.168.1.10 перестаёт отвечать.
  2. Nginx пытается отправить запрос, получает ошибку соединения. Счётчик неудач: 1.
  3. Следующие два запроса тоже завершаются ошибкой. Счётчик достигает max_fails=3.
  4. Nginx исключает сервер из пула на 30 секунд и направляет трафик на 192.168.1.11.
  5. Если 192.168.1.11 тоже отказывает, активируется резервный сервер 192.168.1.12.
  6. Через 30 секунд Nginx пробует отправить запрос на 192.168.1.10. При успешном ответе сервер возвращается в пул.

Keepalive-соединения снижают задержку для каждого запроса, проходящего через эту схему. При восстановлении бэкенда пул соединений перестраивается автоматически.

Если вы строите API Gateway с расширенной маршрутизацией, обратитесь к руководству по настройке отказоустойчивого API Gateway. Там разобраны health checking, failover и кеширование для Nginx Plus, Kong и Envoy Proxy.

Типичные ошибки и как их избежать

Ошибка 1: забытый заголовок Connection при настройке keepalive. Симптом: keepalive не работает, соединения закрываются после каждого запроса. Решение: добавить proxy_set_header Connection "" в блок location. Без этой строки заголовок Connection от клиента пробрасывается бэкенду и переопределяет поведение.

Ошибка 2: слишком агрессивные health checks. Симптом: бэкенды исключаются из пула при кратковременных скачках задержки. Решение: увеличить fail_timeout и уменьшить max_fails. Например, max_fails=5 fail_timeout=60s даёт бэкенду больше времени на восстановление перед исключением.

Ошибка 3: нехватка памяти из-за избыточного keepalive. Симптом: Nginx потребляет гигабайты памяти, хотя трафик не вырос. Решение: уменьшить значение keepalive в upstream. Проверить текущее количество соединений командой ss -tnp | grep nginx | wc -l. Если счётчик переваливает за 5000 при скромной нагрузке, пул keepalive нужно сократить.

Ошибка 4: неверные таймауты proxy. Симптом: клиенты получают ошибки 504 Gateway Timeout при нормально работающих бэкендах. Решение: проверить proxy_read_timeout и proxy_connect_timeout. Значение proxy_read_timeout 60s подходит для большинства приложений, но для долгих операций (генерация отчётов, загрузка файлов) требуется увеличить до 300 секунд и более.

Ошибка 5: отсутствие резервного сервера. Симптом: при отказе всех основных бэкендов клиенты получают ошибки, хотя есть свободные мощности. Решение: добавить сервер с флагом backup в блок upstream. Резервный сервер может быть менее мощным - он нужен для поддержания доступности на время восстановления основных узлов.

Ошибка 6: использование ip_hash с динамическими IP клиентов. Симптом: неравномерное распределение нагрузки, перекос на один бэкенд. Решение: если клиенты выходят через NAT или используют мобильные сети, их IP-адреса меняются. В этом сценарии лучше использовать least_conn или хранить сессии в Redis, а не привязываться к IP.

Для углублённого изучения отказоустойчивых конфигураций с health checks и плавным обновлением бэкендов без потери сессий используйте руководство по продвинутой настройке высокой доступности Nginx.

Поделиться:
Сохранить гайд? В закладки браузера