Learning Platform
Глоссарий Troubleshooting
Урок 14.03 · 22 мин
Начальный
NGINXHAProxyReverse ProxyTLS TerminationCaching

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: посредник со стороны клиента
ClientСотрудник компании в офисной сети. Не имеет прямого доступа в интернет
Forward proxyКорпоративный прокси (Squid, Zscaler). Знает про клиента (свою сеть), фильтрует/логирует запросы, ходит во внешний мир
Любой сервер в интернетеСервер видит запрос от proxy, не от реального клиента
Reverse proxy: посредник со стороны сервера
Любой клиент в интернетеБраузер, мобильное приложение, кто угодно. Знает только публичный адрес reverse proxy
Reverse proxyNGINX, HAProxy. Стоит перед backend-ом, защищает его. Принимает все входящие, маршрутизирует на нужный сервис
Backend poolВнутренние application-серверы. Не видны напрямую в интернете

Главное отличие:

  • Forward proxy ставит клиент. «Я хочу через посредника ходить в интернет.» Зачем: цензура (обход), приватность, контроль доступа в корпорации, кэш на исходящий трафик.
  • Reverse proxy ставит сервер. «Я хочу через посредника принимать запросы.» Зачем: load balancing, TLS termination, кэширование, защита от DDoS, унификация фронтальной точки для микросервисов.

Дальше говорим только про reverse proxy. Это то, что вы будете настраивать в работе с веб-приложениями.


Архитектурный смысл reverse proxy

Зачем вообще ставить прослойку перед своим приложением? Почему не открыть его напрямую в интернет? Несколько причин, каждая важная:

  1. Безопасность. Backend живёт в приватной сети, без публичного IP. Атакующий не может подключиться к нему напрямую — только через reverse proxy, на котором стоит WAF, rate limiting, фильтры.

  2. TLS termination. Управление сертификатами централизовано. Один сертификат на reverse proxy, а не на каждом из 50 backend-ов. Можно использовать Let’s Encrypt с автообновлением.

  3. Уплощение многих сервисов в один домен. У вас 20 микросервисов. Все они доступны по api.example.com/users, /orders, /payments — одно публичное доменное имя, один TLS-сертификат, разные backend-ы по path.

  4. Кэширование. Reverse proxy может отдавать ответы из кэша, не дёргая backend. Особенно ценно для статики и медленных API.

  5. Compression и optimization. gzip/brotli на reverse proxy. Backend отдаёт без сжатия, proxy сжимает.

  6. Логирование и метрики. Все запросы видны в одной точке. Удобно для observability.

  7. Динамическое управление. Можно поменять, какой 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 не подозревает.
WARNING

В 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.
TIP

Не кэшируйте всё подряд. Запросы с авторизацией обычно нельзя — разные пользователи увидят чужие данные. Если кэшируете персонализированные ответы, делайте кэш-ключ с включением 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 — что выбрать

СвойствоNGINXHAProxy
Primary use caseReverse 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Встроенный, очень детальный
ScriptingLua через nginx-mod-luaТолько декларативный конфиг
Скорость reloadZero-downtimeZero-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
Проверка знанийKnowledge check
Junior спрашивает: 'Я просто хочу запустить своё API на сервере. Зачем мне между ним и интернетом ставить NGINX? Это же ещё один процесс, который может упасть, и ещё одна точка отказа'
ОтветAnswer
Резонный вопрос. Но reverse proxy перед backend ставят даже в самых простых проектах -- и причин несколько фундаментальных. Первая -- TLS termination. Современный Python/Node.js/Go веб-сервер технически может терминировать TLS сам, но управление сертификатами через приложение -- боль: ротация Let's Encrypt каждые 90 дней, restart процесса для подцепления нового сертификата, монтирование секретов в контейнер. NGINX делает это нативно, certbot ставит сертификат и тихо подсовывает -- приложение никогда не трогается. Вторая -- защита backend-а. Application-серверы плохо переживают шум: невалидные запросы, ботов, slow loris, SYN-flood. Reverse proxy фильтрует это до того, как трафик дойдёт до backend. Тысячи невалидных запросов положат твой uvicorn, но не положат NGINX. Третья -- скейлинг без переписывания приложения. Сегодня у тебя один backend, завтра тебе нужно три. С reverse proxy это правка одной строчки конфига. Без него -- надо переписывать клиента (он ходит на разные IP), менять DNS, и всё это с переходным периодом. Четвёртая -- единая точка для логов, метрик, security. Все запросы видны в одном access.log, метрики экспортируются в Prometheus, WAF-правила в одном месте. Пятая -- 'ещё одна точка отказа' решается тем, что NGINX -- одна из самых надёжных программ в существовании. Падает гораздо реже application-сервера. В Kubernetes/Docker NGINX в ingress -- стандарт de facto, никто не задумывается, ставить его или нет.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 6. В чём ключевая разница между forward proxy и reverse proxy?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 4