TLS termination — это схема, когда шифрование TLS заканчивается (терминируется) не на приложении, а на внешнем узле: балансировщике, reverse proxy, ingress-контроллере, CDN. Дальше до бэкенда трафик часто идёт обычным HTTP (или повторно шифруется внутри сети). Подход удобный: проще управлять сертификатами и разгружать приложение, но появляются два типовых класса проблем.
- Приложение не понимает, что исходный запрос был HTTPS: делает неверные редиректы, генерирует неправильные абсолютные URL, выставляет «не те» cookie-флаги.
- Бэкенд и логи видят не реальный IP клиента, а адрес прокси, из-за чего ломаются rate-limit, аудит, геоблок, антибот и расследование инцидентов.
Ниже — практическая памятка: какие заголовки использовать (X-Forwarded-Proto, Forwarded), когда нужен PROXY protocol, и как собрать цепочку так, чтобы не получить циклические редиректы, «вечный» 301 и путаницу в логах.
Как выглядит цепочка при TLS termination
Базовый сценарий:
Client (HTTPS) → CDN/LB/Reverse Proxy (terminates TLS) → Backend (HTTP) → App
С точки зрения приложения соединение до него — HTTP, хотя пользователь пришёл по HTTPS. Если ничего не делать, приложение может:
- редиректить на
http://вместоhttps://; - ставить cookie без
Secure; - генерировать канонические ссылки/OG-теги с неправильной схемой;
- непредсказуемо вести себя с HSTS и «принудительным HTTPS» в браузере.
Ключевая идея: прокси обязан донести до бэкенда исходные параметры запроса (схема, хост, порт, IP), а бэкенд должен доверять этим данным только от доверенных прокси.
X-Forwarded-Proto и Forwarded: что выбрать
X-Forwarded-Proto: де-факто стандарт
X-Forwarded-Proto — самый распространённый способ сообщить приложению исходную схему клиента. Обычно прокси выставляет:
X-Forwarded-Proto: https
Часто рядом используются:
X-Forwarded-For— цепочка IP (клиент, прокси1, прокси2…);X-Forwarded-Host— исходный Host;X-Forwarded-Port— исходный порт (обычно 443).
Forwarded (RFC 7239): стандартизирован, но поддержка разная
Forwarded может передавать сразу несколько параметров одним заголовком:
Forwarded: for=203.0.113.10;proto=https;host=example.com
Плюсы: единый формат и явные атрибуты (for, proto, host). Минусы: не все приложения и middleware учитывают его «из коробки», поэтому на практике часто оставляют совместимость через X-Forwarded-*.
Практический выбор
- Если стек современный и вы контролируете приложение — можно включать
Forwarded, но обычно всё равно оставляютX-Forwarded-Protoдля совместимости. - Если приложение «коробочное» (CMS, панель, старый фреймворк) — почти наверняка ему нужен
X-Forwarded-Proto.

Если терминируете TLS на edge и хотите централизованно управлять сертификатами и сроками, проще держать выпуск и ротацию в одном месте. Для проектов с требованиями к валидации и поддержке обычно выбирают коммерческие SSL-сертификаты.
PROXY protocol: когда заголовков недостаточно
PROXY protocol — это не HTTP-заголовок. Это строка (v1) или бинарный префикс (v2), который прокси добавляет перед полезными данными TCP-соединения. Так бэкенд на транспортном уровне узнаёт реальный IP и порт клиента, даже если дальше идёт не HTTP (например, TCP-прокси до TLS-терминации на другом узле, SMTP/IMAP и т.д.).
Когда он нужен:
- у вас L4-балансировка (TCP), и до приложения «не добираются» HTTP-заголовки;
- вы хотите получать реальный IP в Nginx/Apache на более «жёсткой» модели, чем доверие к
X-Forwarded-For; - нужно одинаковое решение для HTTP и не-HTTP сервисов за одним балансировщиком;
- вы строите цепочку из нескольких прокси и хотите строгую модель доверия на уровне транспорта.
Когда достаточно заголовков:
- у вас обычный reverse proxy (HTTP), и приложение корректно доверяет
X-Forwarded-ProtoиX-Forwarded-Forот доверенных адресов.
Главные грабли: доверие к заголовкам и подмена клиентом
Самая частая ошибка — «просто включить» обработку X-Forwarded-For/X-Forwarded-Proto на бэкенде, не ограничив список доверенных прокси. Тогда любой клиент сможет прислать фальшивые заголовки и:
- подделать IP в логах (и обойти rate-limit по IP);
- подделать
https, чтобы приложение построило URL иначе или выставило cookie-флаги иначе; - сломать безопасность, если на этих данных завязаны ACL, аудит, блокировки и корреляция.
Правило простое: бэкенд должен доверять X-Forwarded-* и Forwarded только от явно заданных IP ваших прокси/CDN/LB.
Неправильные редиректы и «редирект-луп» при TLS termination
Классический симптом: «слишком много перенаправлений» и бесконечный 301/302 между HTTP и HTTPS. Обычно цепочка такая:
- клиент приходит по HTTPS на прокси;
- прокси идёт на бэкенд по HTTP;
- приложение не видит, что исходно был HTTPS, и редиректит на HTTPS;
- прокси снова терминирует TLS и идёт на бэкенд по HTTP — цикл повторяется.
Лечение предсказуемое: на прокси выставляем X-Forwarded-Proto (или Forwarded с proto=https), на приложении включаем режим доверия к proxy headers (название зависит от фреймворка), и выбираем одну точку принудительного HTTPS: либо edge, либо приложение.
Если нужно глубже разобрать миграции и влияние HSTS, пригодится материал: миграция домена без ловушек 301 и HSTS.
HSTS за прокси: кто должен добавлять Strict-Transport-Security
Strict-Transport-Security (HSTS) — заголовок, который браузер запоминает и затем принудительно открывает сайт по HTTPS. В контексте TLS termination есть две рабочие модели:
- HSTS на edge (прокси/CDN/LB): логично, потому что именно edge обслуживает HTTPS «наружу» и гарантирует корректную доставку заголовка.
- HSTS на бэкенде: допустимо, если вы уверены, что наружу никогда не уходит HTTP-ответ без TLS, и заголовок не будет добавлен/потерян/задублирован.
Практика: начинайте с небольшого max-age, а затем увеличивайте. Помните, что HSTS «откатывается» не сразу — браузер кеширует политику на срок max-age.
Подробнее про связку HTTPS, HSTS и автоматизацию выпуска — в статье: практическая настройка HTTPS, Certbot и HSTS.
Если проект крутится на отдельном сервере и вы хотите без ограничений управлять цепочкой прокси, логами и политиками безопасности, удобный вариант — вынести edge/балансировщик на VDS и настроить всё под свою топологию.
Рекомендуемые конфигурации: прокси задаёт, бэкенд проверяет
Ниже — рабочие примеры, которые чаще всего закрывают 80% проблем: корректная схема (HTTPS), корректный real ip, и минимальные «дыры» в доверии.
Nginx как reverse proxy (TLS termination) → HTTP backend
На прокси (Nginx, который принимает HTTPS) важно передать Host, схему, порт и цепочку адресов:
server {
listen 443 ssl http2;
server_name example.com;
location / {
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_set_header X-Forwarded-Port $server_port;
proxy_pass http://127.0.0.1:8080;
}
}
Так как подключение к прокси по 443, переменная $scheme будет https — приложение сможет корректно определить исходную схему (если настроено доверие к заголовку).
Nginx backend: real ip через X-Forwarded-For (только от доверенных прокси)
Если вы хотите, чтобы в логах бэкенда был реальный IP, используйте модуль realip и ограничьте доверие через set_real_ip_from:
http {
set_real_ip_from 192.0.2.10;
set_real_ip_from 192.0.2.11;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
log_format main '$remote_addr - $host [$time_local] "$request" $status '
'proto=$http_x_forwarded_proto xfwd=$http_x_forwarded_for';
access_log /var/log/nginx/access.log main;
}
Ключевое: в set_real_ip_from должны быть только ваши прокси/балансировщики. Если добавить «всё подряд», клиент сможет подменить X-Forwarded-For.
PROXY protocol: HAProxy отправляет, Nginx принимает
Если балансировщик общается с бэкендом по PROXY protocol, Nginx должен слушать порт с флагом proxy_protocol, а realip должен брать адрес из транспорта:
server {
listen 80 proxy_protocol;
set_real_ip_from 192.0.2.10;
real_ip_header proxy_protocol;
location / {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_pass http://127.0.0.1:8080;
}
}
Фрагмент HAProxy, который отправляет PROXY protocol в backend:
frontend fe_https
bind :443 ssl crt /etc/haproxy/certs/example.pem
default_backend be_app
backend be_app
server app1 127.0.0.1:8080 send-proxy
Важно: если включили send-proxy, бэкенд обязан ожидать PROXY protocol. И наоборот: если Nginx слушает с proxy_protocol, а upstream его не присылает, вы получите ошибки на уровне протокола (обычно «битый» первый запрос).
Apache за прокси: схема и реальный IP
В Apache схема обычно берётся из заголовков, а real ip — через модуль remoteip. Концептуально выглядит так: доверяем заголовку только от адресов прокси и указываем, какой именно заголовок считать источником адреса клиента.
RemoteIPHeader X-Forwarded-For
RemoteIPTrustedProxy 192.0.2.10
RemoteIPTrustedProxy 192.0.2.11
Дальше уже на уровне приложения/виртуального хоста вы включаете «доверие к HTTPS за прокси» (механика зависит от приложения и связки модулей).

Проверка: как быстро понять, что всё работает
Проверяем схему и редиректы
С клиента удобно посмотреть цепочку редиректов и финальные заголовки:
curl -I -L https://example.com/
Если есть редирект на канонический URL, он должен вести на https://. Если видите прыжки на http://, значит где-то потерян X-Forwarded-Proto (или приложение ему не доверяет).
Проверяем real ip в логах
Сделайте запрос и проверьте, какой IP попадает в access log бэкенда. При корректной настройке realip вы увидите IP клиента, а не адрес прокси. Если везде адрес прокси — значит realip не включён или не добавили нужные адреса в set_real_ip_from/RemoteIPTrustedProxy.
Чеклист безопасной эксплуатации
- Одна точка принудительного HTTPS: либо edge, либо приложение. Смешанный режим часто даёт редирект-луп.
- Доверие только доверенным прокси: ограничьте источники для
X-Forwarded-For/X-Forwarded-Protoи/или используйтеPROXY protocolна L4. - Схема и порт: передавайте
X-Forwarded-Protoи при необходимостиX-Forwarded-Port, иначе приложения иногда строят ссылки на 80. - HSTS включайте осознанно: начните с малого
max-age, затем увеличивайте. Убедитесь, что весь сайт реально обслуживается по HTTPS. - Логи для расследований: полезно логировать и
$remote_addr, и форвард-цепочку (для корреляции), но доверять им можно только от своих прокси.
Что выбрать в реальной инфраструктуре
Если у вас типичный веб-проект и reverse proxy на уровне HTTP — чаще всего достаточно X-Forwarded-Proto + корректная настройка доверия к прокси и real ip. Если у вас L4-балансировка, TCP-прокси, сервисы не-HTTP или нужна более «жёсткая» модель передачи адреса — используйте PROXY protocol.
В любом случае относитесь к forwarded-данным как к небезопасному вводу: пока вы явно не ограничили доверенные источники, клиент может подделать и IP, и схему. Правильная связка TLS termination + X-Forwarded-Proto/Forwarded + real ip даёт предсказуемые редиректы, корректную работу cookie и понятные логи, а HSTS становится управляемой политикой, а не сюрпризом.


