Когда один сервер перестаёт справляться с трафиком, есть два пути: купить более мощную машину или распределить нагрузку между несколькими обычными. Второй вариант дешевле, гибче и надёжнее. Именно для этого существуют балансировщики нагрузки — HAProxy и Nginx upstream.
Оба умеют примерно одно и то же в общих чертах, но устроены совершенно по-разному. Разберём, как они работают, чем отличаются и когда что брать.
Зачем вообще нужна балансировка
Балансировщик нагрузки — это прокси, которая стоит перед твоими серверами (их называют backend или upstream) и раскидывает входящие запросы между ними по какому-то алгоритму.
Плюсы очевидны:
- Горизонтальное масштабирование: добавил сервер — мощность выросла
- Отказоустойчивость: один сервер упал — остальные продолжают работать
- Zero-downtime деплой: обновляешь серверы по очереди, пользователи ничего не замечают
- Равномерное распределение CPU и памяти
Это не rocket science, но настроить правильно — отдельная история.
Nginx как балансировщик: upstream-блок
Nginx все знают как веб-сервер и реверс-прокси. Но у него есть полноценный модуль балансировки — upstream.
Базовая конфигурация выглядит так:
upstream backend {
server 10.0.0.1:8080;
server 10.0.0.2:8080;
server 10.0.0.3:8080;
}
server {
listen 80;
location / {
proxy_pass http://backend;
}
}
По умолчанию Nginx использует round-robin — запросы уходят к серверам по очереди. Просто и честно.
Алгоритмы балансировки в Nginx
round-robin — дефолт. Первый запрос на первый сервер, второй — на второй, и так по кругу.
least_conn — запрос уходит на сервер с наименьшим количеством активных соединений. Полезно когда запросы неравномерные по времени обработки.
upstream backend {
least_conn;
server 10.0.0.1:8080;
server 10.0.0.2:8080;
}
ip_hash — клиент всегда попадает на один и тот же сервер (на основе IP). Нужно когда сессии хранятся локально на сервере.
upstream backend {
ip_hash;
server 10.0.0.1:8080;
server 10.0.0.2:8080;
}
weights — можно задать вес серверу. Сервер с весом 3 получит в три раза больше запросов.
upstream backend {
server 10.0.0.1:8080 weight=3;
server 10.0.0.2:8080 weight=1;
}
Health checks в Nginx
В бесплатной версии Nginx health check пассивный: если сервер не ответил — Nginx помечает его как недоступный на какое-то время. Параметры:
upstream backend {
server 10.0.0.1:8080 max_fails=3 fail_timeout=30s;
server 10.0.0.2:8080 max_fails=3 fail_timeout=30s;
}
max_fails=3 — три неудачных попытки подряд, и сервер считается мёртвым. fail_timeout=30s — через 30 секунд Nginx снова попробует к нему обратиться.
Активные health checks (Nginx сам периодически опрашивает серверы) — только в Nginx Plus, это коммерческая версия. Для бесплатной есть сторонний модуль nginx_upstream_check_module, но его надо компилировать отдельно.
Backup-серверы
Можно пометить сервер как резервный — он получает трафик только если все основные серверы упали:
upstream backend {
server 10.0.0.1:8080;
server 10.0.0.2:8080;
server 10.0.0.3:8080 backup;
}
HAProxy: специализированный балансировщик
HAProxy — это инструмент, который делает только одно, но делает это лучше всех. Он не умеет отдавать статику, не обслуживает PHP — он только балансирует.
Конфигурация HAProxy состоит из нескольких секций:
global
log /dev/log local0
maxconn 50000
user haproxy
group haproxy
defaults
log global
mode http
option httplog
timeout connect 5s
timeout client 30s
timeout server 30s
frontend http_front
bind *:80
default_backend http_back
backend http_back
balance roundrobin
server web1 10.0.0.1:8080 check
server web2 10.0.0.2:8080 check
server web3 10.0.0.3:8080 check
Структура такая: frontend принимает входящий трафик, backend описывает группу серверов. Можно иметь несколько frontend'ов и backend'ов и маршрутизировать между ними по правилам.
Алгоритмы в HAProxy
HAProxy поддерживает больше алгоритмов, чем Nginx:
roundrobin — классический round-robin
leastconn — наименьшее число соединений (хорошо для долгих запросов)
source — по IP клиента (аналог ip_hash в Nginx)
uri — по URI (одинаковые URL всегда идут на один сервер, хорошо для кэширования)
hdr(name) — по значению HTTP-заголовка
random — случайный выбор
first — первый доступный сервер (экономит ресурсы при низкой нагрузке)
Health checks в HAProxy
Здесь главное преимущество. Слово check в конфигурации включает активную проверку прямо из коробки:
backend http_back
balance roundrobin
option httpchk GET /health
server web1 10.0.0.1:8080 check inter 2s rise 2 fall 3
server web2 10.0.0.2:8080 check inter 2s rise 2 fall 3
inter 2s — проверять каждые 2 секунды
rise 2 — 2 успешных ответа чтобы считать сервер живым
fall 3 — 3 неудачных ответа чтобы исключить сервер
option httpchk GET /health — HAProxy делает GET-запрос на /health и смотрит на HTTP-статус. Если 200 — сервер живой.
ACL и маршрутизация
Access Control Lists — одна из главных фич HAProxy. Можно маршрутизировать трафик по любым признакам:
frontend http_front
bind *:80
acl is_api path_beg /api/
acl is_static path_end .jpg .png .css .js
use_backend api_servers if is_api
use_backend static_servers if is_static
default_backend app_servers
API-запросы идут на одни серверы, статика — на другие, всё остальное — на основные. Nginx тоже умеет маршрутизацию через location, но ACL в HAProxy нагляднее для сложных сценариев.
Встроенная статистика
HAProxy имеет встроенный веб-интерфейс:
frontend stats
bind *:8404
stats enable
stats uri /stats
stats refresh 10s
stats auth admin:секретный_пароль
Открываешь браузер, видишь в реальном времени: количество запросов, какие серверы живые, количество ошибок, трафик в байтах. Nginx такого из коробки не даёт — только скромный stub_status.
HAProxy vs Nginx: честное сравнение
| Критерий |
HAProxy |
Nginx |
| Производительность |
Выше (специализированный) |
Высокая |
| Health checks |
Активные из коробки |
Пассивные (активные — платно) |
| Протоколы |
HTTP, TCP, UDP |
HTTP, TCP (stream) |
| Маршрутизация |
Мощная ACL |
Через location |
| Статика |
Нет |
Да |
| Статистика |
Встроенная |
Только stub_status |
| SSL termination |
Да |
Да |
| Конфигурация |
Сложнее |
Проще |
Если нужно просто распределить нагрузку на 2–3 сервера — Nginx справится. Если архитектура сложная, нужна детальная маршрутизация и мониторинг — HAProxy.
На практике часто используют оба: Nginx стоит первым (обрабатывает SSL, отдаёт статику, кэширует), за ним HAProxy балансирует динамику между серверами приложений.
Настройка SSL Termination
Принимать SSL на балансировщике и передавать трафик дальше по HTTP — стандартная практика. Это разгружает серверы приложений от шифрования.
В Nginx:
server {
listen 443 ssl;
ssl_certificate /etc/ssl/cert.pem;
ssl_certificate_key /etc/ssl/key.pem;
location / {
proxy_pass http://backend;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Real-IP $remote_addr;
}
}
В HAProxy:
frontend https_front
bind *:443 ssl crt /etc/ssl/combined.pem
default_backend http_back
http-request set-header X-Forwarded-Proto https
Важный момент: обязательно передавай заголовки X-Forwarded-For и X-Real-IP. Без них приложение видит только IP балансировщика вместо реального клиента — это ломает логи, геоаналитику и защиту от ботов.
Sticky Sessions — когда round-robin не подходит
Если приложение хранит сессии локально на сервере (не в Redis, не в БД), нужно чтобы пользователь всегда попадал на тот же сервер.
В Nginx — ip_hash. Проблема: при мобильных сетях или CDN много пользователей могут быть за одним IP, нагрузка распределяется неравномерно.
В HAProxy — cookie-based sticky sessions, это надёжнее:
backend http_back
balance roundrobin
cookie SERVERID insert indirect nocache
server web1 10.0.0.1:8080 check cookie web1
server web2 10.0.0.2:8080 check cookie web2
HAProxy добавляет cookie SERVERID в ответ. При следующем запросе читает cookie и направляет на тот же сервер.
Но лучше вообще убрать локальные сессии — вынеси их в Redis, и проблема исчезнет. Тогда любой алгоритм балансировки работает корректно.
Zero-downtime деплой
Это одна из главных причин, зачем вообще нужна балансировка. Обновляешь серверы по очереди:
- Убираешь web1 из балансировки
- Деплоишь новую версию на web1
- Проверяешь что всё работает
- Возвращаешь web1 в пул
- Повторяешь для web2
Пользователи работают без перебоев.
HAProxy умеет делать это через сокет без перезагрузки всего процесса:
echo "disable server http_back/web1" | socat stdio /run/haproxy/admin.sock
echo "enable server http_back/web1" | socat stdio /run/haproxy/admin.sock
Типичные ошибки при настройке
Забытые таймауты. Без них соединения будут висеть вечно при зависших серверах. timeout server 30s — разумное значение для большинства приложений.
Нет health check. Без проверки балансировщик продолжает слать запросы на мёртвый сервер. Всегда настраивай.
Нет X-Forwarded-For. Приложение логирует IP балансировщика вместо реального клиента — не видишь кто что делает, сложнее разбирать инциденты.
Одинаковый вес при разных машинах. Если серверы разные по мощности — ставь веса соответственно, иначе слабый сервер захлебнётся.
Хранение сессий локально. Настроил round-robin, пользователи случайно разлогиниваются — потому что попадают на разные серверы. Выноси сессии в Redis.
Как это выглядит в реальном проекте
Команда REEXY настраивала подобную инфраструктуру при запуске нагруженных интернет-магазинов и корпоративных порталов. Типичная схема для среднего проекта:
Клиент → Nginx (SSL, статика, кэш) → HAProxy → [App1, App2, App3] → PostgreSQL
Nginx на входе — потому что умеет быстро отдавать статику и кэшировать ответы. HAProxy за ним — потому что лучше балансирует динамические запросы и даёт детальную статистику по каждому backend-серверу.
Для серьёзных проектов ставят два балансировщика с Keepalived — виртуальный IP переходит на резервный если основной упал. Тогда и сам балансировщик не является single point of failure.
Когда что выбирать — коротко
Бери Nginx upstream если:
- Небольшой проект, 2–3 сервера
- Уже используешь Nginx как веб-сервер
- Не нужна сложная маршрутизация
Бери HAProxy если:
- Высокие требования к отказоустойчивости
- Нужна детальная маршрутизация по URL, заголовкам, cookie
- Важна подробная статистика
- Большое количество серверов в пуле
- Нужна TCP-балансировка (не только HTTP)
Оба варианта production-ready и используются в крупных проектах. Разница — в деталях архитектуры и в том, насколько сложными будут твои требования к маршрутизации и мониторингу.