Серверное ПО и проксирование
Разбираемся кто принимает HTTP-запрос на стороне сервера, как устроены реверс-прокси и чем оборачивается проксирование для информации о клиенте.
Типы серверного ПО
Мы разобрали HTTP, заголовки и TLS. Но кто на стороне сервера всё это обрабатывает? Между клиентом и приложением стоит серверное ПО. По роли оно делится на три типа.
путь + Host → файл.
А что такое бэкенд?
Бэкенд — это серверное приложение которое содержит бизнес-логику. Если веб-сервер просто отдаёт файлы с диска, а реверс-прокси просто передаёт запросы — то бэкенд думает: проверяет права, лезет в базу данных, считает что-то, формирует ответ.
Один продукт — несколько ролей
В реальности один продукт часто совмещает несколько ролей. nginx — классический пример: в одном конфиге он одновременно выполняет все три роли в зависимости от того что делает с запросом.
/static/ → отдаёт файл с диска/api/ → передаёт бэкендуРоль определяется не продуктом, а тем что он делает с конкретным запросом. HAProxy — в первую очередь балансировщик, но умеет и проксировать. Apache — в первую очередь веб-сервер, но умеет и проксировать через модули.
CDN — реверс-прокси ближе к пользователю
CDN (Content Delivery Network) — географически распределённая сеть серверов, каждый из которых работает как кеширующий реверс-прокси. Принцип тот же что у обычного реверс-прокси — принять запрос клиента и отдать ответ — но серверы CDN расположены в разных городах и странах, ближе к пользователям.
CDN-узел хранит копии ответов. Если копии нет или она устарела — CDN сам запрашивает данные у основного сервера (origin), сохраняет и отдаёт клиенту. Клиент не знает что общается с CDN — для него это просто сервер.
Основное применение — статика: картинки, CSS, JavaScript, шрифты, видео. Эти файлы одинаковы для всех пользователей и редко меняются — идеальный случай для кеширования.
С точки зрения архитектуры CDN — ещё один слой в цепочке между клиентом и бэкендом:
Клиент │ ▼ CDN-узел ← кеширующий реверс-прокси, географически близко к пользователю │ если кеш есть → отвечает сразу │ если нет → идёт к origin ▼ HAProxy / nginx / Бэкенд ← основная инфраструктура
Cache-Control, Expires, Vary.
CDN читает директивы которые ставит бэкенд и подчиняется им — точно так же как прокси.
Подробно разберём в главе 6 (Кеширование).
Потеря информации о клиенте
Когда запрос проходит через реверс-прокси, происходят две важные вещи которые нужно понимать.
Проблема 1: потеря IP клиента
Бэкенд видит TCP-соединение не от клиента, а от прокси. В $remote_addr
на бэкенде будет IP прокси — не IP реального пользователя.
Почему это проблема:
- Логи бесполезны — все запросы «от одного IP»
- Rate limiting не работает — нельзя ограничить конкретного клиента
- Геолокация невозможна — прокси в ЦОДе, не у пользователя
- Блокировка по IP невозможна
Решение — специальные заголовки проксирования. Каждый прокси добавляет заголовок с оригинальным IP, и следующее звено может его прочитать.
Проблема 2: потеря протокола
Клиент подключается по HTTPS. Реверс-прокси терминирует TLS и передаёт бэкенду по HTTP. Бэкенд видит HTTP-соединение и не знает что клиент использовал HTTPS.
Это ломает редиректы: бэкенд формирует ссылку http://site.ru/page
вместо https://site.ru/page. Решение — заголовок X-Forwarded-Proto.
Заголовки проксирования
Заголовки проксирования — механизм передачи информации о реальном клиенте через цепочку посредников. Без них бэкенд слеп: не знает кто на самом деле сделал запрос.
X-Real-IP для простоты,
X-Forwarded-For для совместимости.
http или https.
Нужен потому что соединение прокси → бэкенд идёт по HTTP, и бэкенд без этого
заголовка не знает что снаружи было HTTPS. Без него бэкенд формирует
редиректы на http:// — пользователь получает ошибки mixed content.
Host из запроса клиента. Обычно реверс-прокси передаёт
Host как есть через proxy_set_header Host $host и отдельный
X-Forwarded-Host не нужен. Но если прокси подменяет Host
при маршрутизации — оригинальное значение сохраняется здесь.
X-Forwarded-* заголовкам — один заголовок
вместо четырёх. На практике X-Forwarded-For настолько укоренился
что Forwarded используется редко.
Как X-Forwarded-For накапливается в цепочке
X-Forwarded-For: —
# HAProxy добавляет IP клиента:
X-Forwarded-For: 185.220.101.42
X-Forwarded-For: 185.220.101.42, 10.0.0.1
X-Forwarded-For: 185.220.101.42, 10.0.0.1
✓ первый IP = 185.220.101.42 → реальный клиент
X-Forwarded-For: 1.2.3.4 до отправки запроса.
Нельзя слепо доверять первому IP из цепочки. Нужен механизм доверия — set_real_ip_from.
Механизм set_real_ip_from
Как отличить реальный IP клиента от подделки в заголовке?
Директива set_real_ip_from в nginx решает это через список доверенных прокси.
- Перечисляем IP-адреса прокси которым доверяем (наши собственные)
- nginx смотрит откуда физически пришёл TCP-пакет (
$remote_addr) - Если это доверенный прокси — берёт IP из
X-Forwarded-For - Если это кто-то посторонний — игнорирует
X-Forwarded-For
real_ip_header X-Forwarded-For; # из какого заголовка брать IP
real_ip_recursive on; # перебирать цепочку справа налево
Берём
10.0.0.1 — есть ли в set_real_ip_from?
185.220.101.42 — есть ли в set_real_ip_from?
Защита от подделки
Что если злоумышленник сам вставит поддельный IP в заголовок?
X-Forwarded-For: 1.2.3.4
# HAProxy дописывает реальный IP:
X-Forwarded-For: 1.2.3.4, 99.88.77.66
99.88.77.66 — в set_real_ip_from? Нет
→ стоп, это реальный клиент
$remote_addr = 99.88.77.66
# Подделка не сработала ✓
Механизм работает потому что HAProxy (наш доверенный прокси) всегда дописывает реальный IP в конец цепочки. Злоумышленник может подделать начало — но не конец.
Злоумышленник подключается напрямую к nginx
# Злоумышленник (99.88.77.66) идёт напрямую в nginx, минуя HAProxy: X-Forwarded-For: 1.2.3.4 # nginx проверяет: $remote_addr = 99.88.77.66 # 99.88.77.66 — в set_real_ip_from? Нет. # → X-Forwarded-For игнорируется полностью. $remote_addr = 99.88.77.66 # реальный IP злоумышленника
Полная картина
Соберём всё вместе. Вот как выглядит реальная цепочка с заголовками на каждом уровне.
На каждом уровне — своя роль. Каждый делает своё дело и передаёт дальше. Заголовки проксирования — клей который не даёт информации потеряться по дороге.
Итог
| Заголовок | Что передаёт |
|---|---|
| X-Forwarded-For | Цепочка IP: клиент, прокси-1, прокси-2... |
| X-Real-IP | Один IP клиента — проще парсить |
| X-Forwarded-Proto | Протокол клиента (http / https) |
| X-Forwarded-Host | Оригинальный Host из запроса клиента |