Nginx reverse proxy с SSL и балансировкой нагрузки
Привет! Я Евгений Семенов, директор ITFresh. За мои 15 с лишним лет работы с Nginx-балансировщиками я повидал всякое. И крошечные интернет-магазины с парой десятков заказов в день, и гигантские корпоративные API, где запросы летят со скоростью 8 000 в секунду! Что я вынес из всего этого опыта? Одно правило неизменно: сначала думаешь о надёжности, потом уже о производительности. Точка. В этой статье мы раскроем наш проверенный production-конфиг. Его мы уже два года успешно внедряем у клиентов. Внутри всё как положено: SSL, балансировка, автоматические проверки health checks, и, само собой, развёртывание без простоев – zero-downtime.
Когда нужна именно балансировка, а не обычный прокси
Давайте честно: если бэкенд у вас всего один, о каком балансировщике речь? Он вам просто не нужен. Вполне хватит обычного reverse proxy. Но вот когда у вас не один сервер, балансировка — это уже мастхэв. А именно, в трёх случаях:
- Рост нагрузки. Один бэкенд перестал справляться — добавляете второй и третий, делите трафик.
- Отказоустойчивость. Одна нода упала — Nginx сам перенаправляет трафик на живые.
- Rolling deploy. Обновляете бэкенды по одному, трафик продолжает идти на остальные.
Знаете, что мы видим чаще всего? Клиенты приходят к нам с запросом, который идеально вписывается во второй и третий сценарий одновременно. Они говорят: «Парни, для нас критично, чтобы сервис работал 24/7. Никаких простоев, когда мы там что-то обновляем или перезагружаем!» Наш ответ в таких случаях всегда один и тот же: это значит, что без двух бэкендов и надёжного балансировщика вам не обойтись.
Архитектура и варианты развёртывания
Итак, какой минимум нужен для работающей схемы с балансировкой? Это три сервера: один Nginx-фронт и к нему, само собой, две бэкенд-ноды. Если же задачи посерьёзнее, мы обычно рекомендуем ставить два Nginx, обязательно с keepalived для виртуального IP (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 вполне достаточно. Но если речь идёт о SLA 99,9% и выше, то тут уже берёмся за HA (High Availability). А вот 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 переключился на офисный Wi-Fi – его сессия, увы, просто потеряется.
Наш совет клиентам всегда один, и мы настаиваем на нём без исключений: если есть хоть малейший шанс, обязательно выносите сессии в Redis. Или, на худой конец, в базу данных. В этом случае любая схема балансировки будет просто летать, а про sticky-сессии можно будет забыть навсегда, за ненадобностью!
Health checks и passive monitoring
Увы, в Nginx OSS (то есть, в бесплатной версии) вы не найдёте встроенного active health check. Там работает только passive. Как это происходит? Nginx отслеживает количество ошибок (fails) для каждого бэкенда, ориентируясь на параметр max_fails в течение определённого fail_timeout. Если сервер превысил этот лимит, Nginx моментально помечает его как недоступный (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.
