Reverse proxy: NGINX, HAProxy, TLS termination и кэширование
В предыдущих уроках мы говорили про load balancer как абстракцию. Теперь спустимся к конкретике: 95% случаев в реальной production-системе LB реализован как reverse proxy на NGINX или HAProxy. Эти две программы стоят между интернетом и вашим backend-ом во всех веб-проектах, от стартапа до Netflix.
Reverse proxy — это не просто «балансировщик». Это универсальная edge-точка, которая делает много чего одновременно: распределяет нагрузку, терминирует TLS, кэширует ответы, ограничивает rate limit, переписывает URL-ы, добавляет заголовки. В этом уроке разберём, что такое reverse proxy концептуально и как практически настроить рабочий конфиг NGINX и HAProxy.
Forward vs reverse proxy — две стороны одной медали
Слово «proxy» означает «посредник». Когда говорим про прокси в сети, всегда вопрос — кто посредник между кем.
Главное отличие:
- Forward proxy ставит клиент. «Я хочу через посредника ходить в интернет.» Зачем: цензура (обход), приватность, контроль доступа в корпорации, кэш на исходящий трафик.
- Reverse proxy ставит сервер. «Я хочу через посредника принимать запросы.» Зачем: load balancing, TLS termination, кэширование, защита от DDoS, унификация фронтальной точки для микросервисов.
Дальше говорим только про reverse proxy. Это то, что вы будете настраивать в работе с веб-приложениями.
Архитектурный смысл reverse proxy
Зачем вообще ставить прослойку перед своим приложением? Почему не открыть его напрямую в интернет? Несколько причин, каждая важная:
-
Безопасность. Backend живёт в приватной сети, без публичного IP. Атакующий не может подключиться к нему напрямую — только через reverse proxy, на котором стоит WAF, rate limiting, фильтры.
-
TLS termination. Управление сертификатами централизовано. Один сертификат на reverse proxy, а не на каждом из 50 backend-ов. Можно использовать Let’s Encrypt с автообновлением.
-
Уплощение многих сервисов в один домен. У вас 20 микросервисов. Все они доступны по
api.example.com/users,/orders,/payments— одно публичное доменное имя, один TLS-сертификат, разные backend-ы по path. -
Кэширование. Reverse proxy может отдавать ответы из кэша, не дёргая backend. Особенно ценно для статики и медленных API.
-
Compression и optimization. gzip/brotli на reverse proxy. Backend отдаёт без сжатия, proxy сжимает.
-
Логирование и метрики. Все запросы видны в одной точке. Удобно для observability.
-
Динамическое управление. Можно поменять, какой backend обслуживает какие пути, без редеплоя backend-а.
NGINX как reverse proxy — минимальный рабочий конфиг
NGINX — самый популярный reverse proxy. Создан в 2004 году Игорем Сысоевым (бывший Rambler) специально для решения проблемы C10K — одновременных соединений. Использует event-driven архитектуру (вместо thread-per-connection), поэтому масштабируется до десятков тысяч одновременных соединений на одном процессе.
Минимальный рабочий конфиг reverse proxy:
events {
worker_connections 1024;
}
http {
upstream backend {
server 10.0.0.1:8000;
server 10.0.0.2:8000;
server 10.0.0.3:8000;
}
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://backend;
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;
}
}
}
Разберём построчно:
upstream backend { ... }— именованный пул backend-серверов. NGINX будет round-robin-ить между ними.server { listen 80; ... }— виртуальный сервер на порту 80, обрабатывающий запросы кapi.example.com.location / { ... }— блок, обрабатывающий все запросы (location/— это «всё, что начинается со слеша»).proxy_pass http://backend— ключевая директива: «передавай запрос на upstream под именем backend».proxy_set_header Host $host— передаём оригинальный Host клиента backend-у. Иначе backend получитHost: backend(имя upstream) и сломается.X-Real-IPиX-Forwarded-For— стандартные заголовки, чтобы backend знал реальный IP клиента. Без них он увидит IP NGINX-а.X-Forwarded-Proto— говорит backend-у, по какому протоколу пришёл оригинальный запрос (http/https).
Это база. Дальше — наращиваем фичами.
TLS termination в NGINX
В 2026 году HTTP-only — табу, всё работает по HTTPS. Reverse proxy идеален для TLS termination:
server {
listen 443 ssl http2;
server_name api.example.com;
# Сертификат от Let's Encrypt
ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
# Современные настройки безопасности
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
# HSTS -- говорим браузеру: всегда ходи по HTTPS
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# Редирект с HTTP на HTTPS
server {
listen 80;
server_name api.example.com;
return 301 https://$host$request_uri;
}
Что важно:
listen 443 ssl http2— слушаем 443 в режиме SSL и поддерживаем HTTP/2.ssl_certificateиssl_certificate_key— путь до сертификата и приватного ключа. Сертификаты получают от Let’s Encrypt через certbot или вручную от любого CA.ssl_protocols TLSv1.2 TLSv1.3— запрещаем устаревшие TLS 1.0/1.1.proxy_pass http://backend— внутри сети идёт plain HTTP. Backend о TLS не подозревает.
В production обязательно ставьте add_header X-Frame-Options DENY, X-Content-Type-Options nosniff, Strict-Transport-Security. Это базовые security headers, без них браузер защищает пользователя хуже. Mozilla Observatory показывает score ‘F’, если их нет.
Path-based routing — несколько микросервисов на одном домене
Самая частая практическая задача reverse proxy — разводить запросы на разные backend-ы по пути URL.
http {
upstream users_service {
server 10.0.0.1:8001;
server 10.0.0.2:8001;
}
upstream orders_service {
server 10.0.0.3:8002;
server 10.0.0.4:8002;
}
upstream payments_service {
server 10.0.0.5:8003;
}
server {
listen 443 ssl;
server_name api.example.com;
# ... TLS config ...
location /users/ {
proxy_pass http://users_service;
proxy_set_header Host $host;
}
location /orders/ {
proxy_pass http://orders_service;
proxy_set_header Host $host;
}
location /payments/ {
proxy_pass http://payments_service;
proxy_set_header Host $host;
}
# Всё остальное -- 404
location / {
return 404 "Not Found";
}
}
}
Теперь:
GET https://api.example.com/users/123идёт на users_service.POST https://api.example.com/ordersидёт на orders_service.POST https://api.example.com/payments/chargeидёт на payments_service.
Клиенты видят один домен, в реальности это маршрутизация на разные сервисы. Можно деплоить независимо, скейлить независимо, писать на разных языках.
Кэширование на уровне reverse proxy
Если ваш backend отдаёт ответы, которые можно кэшировать (страницы со списком, статика, редко меняющиеся данные) — NGINX может отдавать их сам, без обращения к backend.
http {
proxy_cache_path /var/cache/nginx
levels=1:2
keys_zone=api_cache:100m
max_size=10g
inactive=60m
use_temp_path=off;
server {
listen 443 ssl;
server_name api.example.com;
location /api/products/ {
proxy_pass http://backend;
proxy_cache api_cache;
# Кэшируем 200/301/302 на 5 минут, 404 на 1 минуту
proxy_cache_valid 200 301 302 5m;
proxy_cache_valid 404 1m;
# Использовать кэш, даже если backend медленный/мёртвый
proxy_cache_use_stale error timeout updating http_500 http_502 http_503;
# Видимый в Response заголовок: hit или miss
add_header X-Cache-Status $upstream_cache_status;
# Кэш-ключ: метод + URI (Vary по Authorization чтобы не путать пользователей)
proxy_cache_key "$request_method$request_uri";
}
}
}
Что это даёт:
- При повторном запросе того же URL NGINX отдаёт ответ из локального файлового кэша.
- Backend не дёргается — экономим CPU, BD-нагрузку, time.
- Если backend упал — NGINX продолжает отдавать кэшированные ответы (
use_stale). - В заголовке
X-Cache-Statusвидно:HIT(из кэша),MISS(запрос пошёл на backend и закэширован),EXPIRED,STALE.
Не кэшируйте всё подряд. Запросы с авторизацией обычно нельзя — разные пользователи увидят чужие данные. Если кэшируете персонализированные ответы, делайте кэш-ключ с включением user ID (или Authorization, если он одинаковый для пользователя).
HAProxy как reverse proxy
HAProxy — альтернатива NGINX, исторически считавшаяся «более правильной» именно для load balancing. Главные особенности:
- Очень детальная статистика (встроенный stats dashboard).
- Богатые health check возможности.
- Прекрасная производительность для HTTP и TCP.
- Конфиг строго декларативный, без скриптинга (в отличие от NGINX с Lua).
Минимальная HAProxy-конфигурация:
global
log stdout local0
maxconn 50000
defaults
mode http
log global
option httplog
option dontlognull
timeout connect 5s
timeout client 30s
timeout server 30s
frontend https_in
bind *:443 ssl crt /etc/haproxy/certs/example.pem alpn h2,http/1.1
http-request set-header X-Forwarded-Proto https
# Path-based routing
use_backend users_pool if { path_beg /users/ }
use_backend orders_pool if { path_beg /orders/ }
use_backend payments_pool if { path_beg /payments/ }
default_backend not_found
backend users_pool
balance roundrobin
option httpchk GET /healthz
server u1 10.0.0.1:8001 check
server u2 10.0.0.2:8001 check
backend orders_pool
balance leastconn
option httpchk GET /healthz
server o1 10.0.0.3:8002 check
server o2 10.0.0.4:8002 check
backend payments_pool
balance roundrobin
server p1 10.0.0.5:8003 check
backend not_found
http-request deny deny_status 404
# Stats endpoint
frontend stats_in
bind *:8404
mode http
stats enable
stats uri /stats
stats refresh 10s
Разберём ключевые директивы:
global— настройки уровня процесса (логи, максимум соединений).defaults— значения по умолчанию для всех frontend/backend.frontend https_in— входная точка, слушает 443 с TLS.crt— путь до объединённого PEM (cert + key).alpn h2,http/1.1— поддерживаем HTTP/2 и HTTP/1.1.use_backend ... if { path_beg /xxx }— условный routing.path_beg— путь начинается с подстроки.balance roundrobinилиleastconn— алгоритм выбора backend (про них подробно в следующем уроке).option httpchk GET /healthz— health check: периодически HEAD /healthz. Backend помечается DOWN если не отвечает.server u1 10.0.0.1:8001 check— собственно backend.checkвключает health check.
# Запустить HAProxy с этим конфигом:
haproxy -f /etc/haproxy/haproxy.cfg -V
# Проверить, не сломан ли конфиг (без запуска):
haproxy -c -f /etc/haproxy/haproxy.cfg
# Reload (zero-downtime):
systemctl reload haproxy
NGINX vs HAProxy — что выбрать
| Свойство | NGINX | HAProxy |
|---|---|---|
| Primary use case | Reverse proxy + HTTP сервер + статика | Load balancing (TCP+HTTP) |
| HTTP/2, HTTP/3 | Да, отличная поддержка | HTTP/2 есть, HTTP/3 в expert mode |
| TCP balancing | Через stream-модуль | Нативно, главная задача |
| Health checks | Базовые (пассивные) | Очень богатые (active + passive) |
| Stats dashboard | Через nginx-module-vts или Prometheus | Встроенный, очень детальный |
| Scripting | Lua через nginx-mod-lua | Только декларативный конфиг |
| Скорость reload | Zero-downtime | Zero-downtime |
| Документация | Огромная, много примеров | Хорошая, чуть суше |
| Сообщество | Очень большое | Большое |
Практический совет: NGINX — если основная задача reverse proxy + статика + типичный микросервисный фронтенд. HAProxy — если основная задача именно балансировка TCP/HTTP с богатой логикой health checks и подробной статистикой. В чисто load-balancing задачах HAProxy часто чуть быстрее и стабильнее.
Многие большие компании используют оба: HAProxy на самом edge для TCP-балансировки и сложных health checks, NGINX внутри для HTTP-роутинга и кэширования.
Реальный пример: rate limiting на NGINX
Защита от перегрузки и злоумышленников — одна из частых задач reverse proxy. NGINX умеет ограничивать запросы по IP-адресу или по другому ключу.
http {
# Зона: 10MB памяти, 100 req/s на IP, burst до 200
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=100r/s;
server {
listen 443 ssl;
server_name api.example.com;
location /api/ {
limit_req zone=api_limit burst=200 nodelay;
limit_req_status 429; # вернуть 429 Too Many Requests при превышении
proxy_pass http://backend;
}
}
}
Что делает:
- Каждый клиентский IP получает «лимит» 100 запросов в секунду.
- При burst до 200 — запросы накапливаются в очереди.
- При превышении — NGINX отвечает 429 без обращения к backend.
- Зона памяти 10MB хранит счётчики — хватает на ~160k одновременных уникальных IP.
В реальной системе rate limiting на reverse proxy — первая линия защиты. Даже если backend хорошо защищён, спам-боты не дойдут до него — их остановит NGINX на edge.
Попробуй сам
Давайте поднимем простой reverse proxy на NGINX в Docker — это самый быстрый способ попробовать.
# Создайте файл nginx.conf:
cat > /tmp/nginx.conf <<'EOF'
events {}
http {
upstream backends {
server backend1:80;
server backend2:80;
}
server {
listen 80;
location / {
proxy_pass http://backends;
proxy_set_header Host $host;
add_header X-LB-Test "yes" always;
}
}
}
EOF
# Создайте простую docker-сеть и запустите два backend-а:
docker network create lab12
docker run -d --name backend1 --network lab12 \
-e BACKEND_NAME=one \
nginx:alpine sh -c "echo 'I am backend 1' > /usr/share/nginx/html/index.html && nginx -g 'daemon off;'"
docker run -d --name backend2 --network lab12 \
-e BACKEND_NAME=two \
nginx:alpine sh -c "echo 'I am backend 2' > /usr/share/nginx/html/index.html && nginx -g 'daemon off;'"
# Запустите reverse proxy:
docker run -d --name proxy --network lab12 -p 8080:80 \
-v /tmp/nginx.conf:/etc/nginx/nginx.conf:ro \
nginx:alpine
Стрельните несколько раз:
for i in {1..6}; do
curl -s http://localhost:8080/ | head -1
done
# I am backend 1
# I am backend 2
# I am backend 1
# I am backend 2
# I am backend 1
# I am backend 2
NGINX по дефолту чередует backend-ы round-robin. Теперь попробуйте симулировать падение одного:
docker stop backend1
# Повторите запросы:
for i in {1..6}; do
curl -s http://localhost:8080/ | head -1
done
# Все ответы будут "I am backend 2" -- NGINX автоматически выкинул упавший
Поднимите обратно docker start backend1 — через несколько секунд NGINX вернёт его в ротацию.
И посмотрите ваш кастомный заголовок:
curl -v http://localhost:8080/ 2>&1 | grep X-LB-Test
# < X-LB-Test: yes
Kubernetes Ingress — reverse proxy как первый класс кластера TLS termination: сертификаты, HSTS, cipher suites