Nginx reverse proxy с SSL и балансировкой нагрузки
Меня зовут Семёнов Евгений Сергеевич, директор АйТи Фреш. За 15+ лет я настраивал балансировщики на Nginx под самые разные нагрузки — от небольшого онлайн-магазина на 20 заказов в день до корпоративных API, через которые проходит 8 000 запросов в секунду. И главное правило не поменялось: сначала думаем про надёжность, потом про производительность. В этой статье — рабочий production-конфиг с SSL, балансировкой, health checks и zero-downtime развёртыванием, который я ставлю клиентам последние два года.
Когда нужна именно балансировка, а не обычный прокси
Если у вас один бэкенд — вам не нужен load balancing. Вам нужен просто reverse proxy. Балансировка нужна в трёх случаях:
- Рост нагрузки. Один бэкенд перестал справляться — добавляете второй и третий, делите трафик.
- Отказоустойчивость. Одна нода упала — Nginx сам перенаправляет трафик на живые.
- Rolling deploy. Обновляете бэкенды по одному, трафик продолжает идти на остальные.
У нас на практике самый частый сценарий — второй плюс третий. Клиент говорит: «нам нужно, чтобы не было простоев при перезагрузке сервера». Ответ — два бэкенда и балансировщик.
Архитектура и варианты развёртывания
Минимальная схема с балансировкой — это три сервера: один Nginx-фронт и две бэкенд-ноды. Более серьёзная — два Nginx с keepalived (VIP), три-четыре бэкенда. Я использую три типовых топологии:
| Топология | Фронт | Бэкенд | Когда |
|---|---|---|---|
| Basic | 1× Nginx | 2× app | Офис 50+ РМ, внутренний портал |
| HA | 2× Nginx + keepalived | 2–4× app | Публичный сайт, продакшен |
| Multi-DC | 2× Nginx в ДЦ1 + 2× в ДЦ2, DNS round robin | 4+ app | Крупные B2B, банки |
Для 90% заказчиков хватает Basic. HA беру, когда SLA 99,9% и выше. Multi-DC — редкие проекты, обычно это уже не Nginx, а Cloudflare перед инфраструктурой.
Установка и базовая подготовка
Я всегда ставлю Nginx stable из официального репозитория nginx.org. Ядра Linux 5.15+ дают полноценный epoll с SO_REUSEPORT, что критично для высокой нагрузки:
apt install -y nginx
# или из официального репозитория с актуальной версией
# см. https://nginx.org/en/linux_packages.html
Тюн ядра Linux /etc/sysctl.d/99-nginx.conf:
net.core.somaxconn = 65535
net.core.netdev_max_backlog = 16384
net.ipv4.tcp_max_syn_backlog = 8192
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
net.ipv4.ip_local_port_range = 1024 65535
fs.file-max = 1000000
sysctl --system
# В /etc/security/limits.conf
nginx soft nofile 100000
nginx hard nofile 100000
Конфиг upstream с балансировкой
Сам upstream-блок — сердце балансировщика. Разберу параметры:
upstream app_cluster {
# Стратегия: least_conn более справедлив при разной длительности запросов
least_conn;
# Бэкенды с параметрами
server 10.10.10.21:8080 weight=2 max_fails=3 fail_timeout=20s max_conns=256;
server 10.10.10.22:8080 weight=2 max_fails=3 fail_timeout=20s max_conns=256;
server 10.10.10.23:8080 weight=1 max_fails=3 fail_timeout=20s max_conns=256 backup;
# Keepalive к бэкендам — критично для производительности
keepalive 64;
keepalive_requests 1000;
keepalive_timeout 60s;
}
Ключевые параметры сервера:
- weight=N — вес для round robin и least_conn. Сервер мощнее — вес больше.
- max_fails + fail_timeout — через сколько фейлов за какой период пометить как неживой.
- max_conns — максимум одновременных соединений. Защищает бэкенд от перегрузки.
- backup — используется только при недоступности основных.
- down — помечает сервер выключенным (используется при deploy).
Полный рабочий server-блок
server {
listen 80;
server_name app.example.ru;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name app.example.ru;
# TLS 1.2/1.3, сильные шифры, OCSP stapling
ssl_certificate /etc/letsencrypt/live/app.example.ru/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/app.example.ru/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/app.example.ru/chain.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1d;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
# HSTS, безопасность
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Frame-Options SAMEORIGIN always;
add_header X-Content-Type-Options nosniff always;
add_header Referrer-Policy strict-origin-when-cross-origin always;
# Тяжёлые запросы
client_max_body_size 500M;
client_body_timeout 120s;
# Балансировка
location / {
proxy_pass http://app_cluster;
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_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_tries 3;
proxy_next_upstream_timeout 10s;
}
}
Sticky sessions, когда без них не обойтись
Если приложение хранит сессию в локальной памяти и между бэкендами её нет — используйте ip_hash или sticky cookie (последний только в Nginx Plus). Бесплатный вариант — ip_hash:
upstream app_cluster {
ip_hash;
server 10.10.10.21:8080;
server 10.10.10.22:8080;
server 10.10.10.23:8080;
}
Проблема ip_hash — за NAT провайдера может прилетать десяток пользователей с одного IP, и они все попадают на одну ноду. Плюс если пользователь меняет сеть (LTE → офис), его сессия теряется.
Я всегда говорю клиентам — если есть возможность, переведите сессии в Redis или БД. Тогда любая балансировка работает корректно, а sticky вам не нужен.
Health checks и passive monitoring
В Nginx OSS встроенного active health check нет — только passive. Схема: Nginx считает fails по max_fails в пределах fail_timeout. Превысили — сервер помечен unavailable.
# В upstream
server 10.10.10.21:8080 max_fails=2 fail_timeout=10s;
# Если 2 запроса подряд дали 5xx/timeout в течение 10с — сервер удаляется на 10с
Для активных проверок я ставлю сторонний модуль nginx_upstream_check_module или использую внешний мониторинг (Prometheus blackbox_exporter) + алерты. На production клиенту обычно хватает passive + мониторинг.
Zero-downtime deploy
Типовой скрипт rolling deploy — обновляем бэкенды по одному. Пример на bash:
#!/bin/bash
BACKENDS=("10.10.10.21:8080" "10.10.10.22:8080" "10.10.10.23:8080")
for b in "${BACKENDS[@]}"; do
echo "Draining $b..."
sed -i "s|server $b .*|server $b down;|" /etc/nginx/conf.d/app.conf
nginx -s reload
sleep 30 # ждём завершения текущих запросов
ssh $b "systemctl restart app"
sleep 10
sed -i "s|server $b down;|server $b max_fails=2 fail_timeout=20s;|" /etc/nginx/conf.d/app.conf
nginx -s reload
echo "$b back in rotation"
done
nginx -s reload не разрывает текущие соединения — старые воркеры отработают свои запросы, новые примут новые. Идеальный zero-downtime.
Реальный кейс: интернет-магазин на Битрикс
В октябре 2025 ко мне обратился интернет-магазин товаров для бани — 1600 SKU, 500–800 заказов в сутки, высокие пики в пятницу-субботу. Всё крутилось на одном Битрикс-сервере, и каждая акция вызывала 502-ки. Нужен был балансировщик и второй бэкенд.
Подняли вторую ноду, идентичную первой (Dell с Xeon Platinum 8280, 64 ГБ RAM), общий файловый каталог через NFS на GlusterFS. Перед ними — Nginx reverse proxy с SSL, least_conn, sticky нет (сессии в Redis). Добавили кеш статики /upload/ на 7 дней, rate limit 100 r/s на /search/. Выделенная подсеть 40G Mellanox между балансировщиком и бэкендами — это важно для Битрикса с его AJAX-запросами.
После запуска: среднее время ответа упало с 1.4 с до 380 мс, 502-ки исчезли, пятничные пики проходят без падений. Тест Black Friday 2025: 2400 RPS в пике, нули ошибок. Стоимость работ — 135 000 руб., плюс железо второго сервера. По словам клиента, окупилось за первую распродажу.
Частые ошибки балансировки
- keepalive=0 или отсутствует. Каждое соединение к бэкенду закрывается — огромный оверхед. Ставим keepalive 32+ и proxy_set_header Connection "".
- ip_hash без необходимости. Если сессии в Redis, ip_hash лишний — теряете преимущества балансировки.
- Одинаковые весы на разных серверах. Сервер в 2 раза мощнее — ставьте weight=2.
- Короткий fail_timeout. Мигнула сеть — сервер помечен как мёртвый на 10 секунд, реальные пользователи получают 502.
- SSL-сессии без shared cache. Каждый запрос пересогласует TLS — дорого. Обязательно ssl_session_cache shared:SSL:50m.
Построим HA на Nginx с балансировкой
Я лично проектирую и внедряю балансировщики на Nginx для веб-приложений и API. SSL, health checks, rolling deploy, keepalived для HA-пары. От 2 до 10 бэкендов, срок — 2–5 рабочих дней.
Телефон: +7 903 729-62-41
Telegram: @ITfresh_Boss
Семёнов Евгений Сергеевич, директор АйТи Фреш
FAQ
- Какую стратегию балансировки выбрать?
- По умолчанию round robin. Для API с равной нагрузкой на запрос — least_conn. Для приложений с сессиями в памяти — ip_hash или sticky cookie. Для весов по железу — server ... weight=N.
- Что такое sticky session и когда его использовать?
- Sticky session привязывает пользователя к одному бэкенду, чтобы сессия не терялась при переключении. Нужен если приложение хранит сессию локально. Если сессия в Redis/БД — sticky не нужен, и можно балансировать как угодно.
- Как сделать deploy без даунтайма?
- Помечаете один бэкенд как down в upstream, ждёте 30 секунд для дренирования, обновляете его, возвращаете в работу, переходите к следующему. Nginx reload делается через SIGHUP — открытые соединения не рвутся.
- Нужен ли в Nginx OSS active health check?
- Встроенный active health check — только в Nginx Plus. В бесплатной версии используется passive (max_fails + fail_timeout). Для active ставят сторонний модуль nginx_upstream_check или внешние сервисы.
- TLS 1.0 и 1.1 — отключать?
- Да, обязательно. Оставляем TLS 1.2 и TLS 1.3. 1.0/1.1 имеют уязвимости и не соответствуют PCI-DSS. У меня в конфиге всегда только TLSv1.2 TLSv1.3.