Классическая ситуация: перед вашим веб‑сервером стоит CDN или балансировщик, а в логах и приложении вместо клиентского адреса виден IP обратного прокси. Это ломает гео‑таргетинг, антифрод, rate limit и статистику. В этой статье систематизируем подходы к получению real IP за прокси, разберём заголовки X-Forwarded-For и стандарт Forwarded, протокол PROXY (v1/v2), настройки для nginx и Apache, а также корректную запись IP в логи.
Почему за прокси теряется реальный IP
Когда фронтенд‑прокси или CDN принимает соединение от клиента и отправляет его дальше на ваш веб‑сервер, соединение устанавливается уже между прокси и бэкендом. Со стороны веб‑сервера источником TCP‑подключения становится адрес прокси, а не клиента. Чтобы передать реальный адрес, используют либо заголовки уровня HTTP (X-Forwarded-For, Forwarded), либо транспортный PROXY Protocol, добавляющий метаданные в начало TCP‑потока.
Главная задача — восстановить адрес клиента надёжно и безопасно. Нельзя бездумно доверять любому заголовку, который может подделать сам клиент.
Заголовки уровня HTTP: X‑Forwarded‑For и Forwarded
X-Forwarded-For (XFF) — де‑факто стандартный заголовок, который прокси добавляет к запросу. Формат — список IP через запятую: первый адрес — исходный клиент, далее цепочка прокси. Пример:
X-Forwarded-For: 203.0.113.10, 198.51.100.5
Стандартный заголовок из RFC 7239 — Forwarded. Он более формальный и поддерживает IPv6 и дополнительные параметры:
Forwarded: for=203.0.113.10; proto=https; by=198.51.100.5
Forwarded: for="[2001:db8::1]"; proto=http
Плюсы заголовков:
- Просто включить на большинстве прокси и балансировщиков.
- Читаемо, видно полную цепочку.
Минусы:
- Клиент может подделать заголовок. Верить ему можно только если запрос пришёл от доверенного прокси, который заголовок корректно формирует и/или переписывает.
- Разночтения формата (пробелы, IPv6, кавычки), нужны аккуратные парсеры.
Транспортный уровень: PROXY Protocol v1/v2
PROXY Protocol — это префикс к TCP‑соединению. Прокси добавляет в начале сессии строку (v1, текстовая) или бинарный блок (v2), где указывает исходный и целевой адреса/порты. Так бэкенд получает достоверный адрес клиента, не полагаясь на заголовки HTTP.
Плюсы:
- Подделать со стороны клиента невозможно: он не достучится до бэкенда минуя балансировщик.
- Работает для любых протоколов поверх TCP (HTTP, SMTP и т.д.).
Минусы:
- Нужно включить поддержку и на прокси, и на бэкенде. Иначе бэкенд увидит «мусор» в начале потока.
- Не все софты и библиотеки автоматически поддерживают PROXY v2 (но веб‑сервера поддерживают давно).

Базовая архитектура: где восстанавливать real IP
Есть два рабочих паттерна:
- Только HTTP‑заголовки: фронт добавляет/переписывает
X-Forwarded-Forи опциональноForwarded, бэкенд (nginx/Apache) доверяет заголовку только от известных адресов прокси и извлекает реальный IP. - PROXY Protocol: фронт шлёт PROXY, бэкенд слушает сокет с опцией
proxy_protocol(nginx) илиProxyProtocol On(Apache), и использует адрес из транспортных метаданных.
Если у вас цепочка: клиент → CDN → L4/L7 балансировщик → веб‑сервер, выберите один доверенный источник правды. Чаще всего — PROXY Protocol между балансировщиком и веб‑серверами. Заголовок XFF можно оставить для трассировки всей цепочки.
Для самостоятельной сборки периметра рационально использовать VDS, где вы контролируете и фронты, и бэкенды.
nginx: настройка real IP через X‑Forwarded‑For
Чтобы nginx корректно определял реальный IP из заголовка XFF, необходимо:
- Перечислить доверенные адреса прокси (CDN, балансировщиков) через
set_real_ip_from. - Указать источник IP:
real_ip_header X-Forwarded-For(илиForwardedпри соответствующей конфигурации). - Включить
real_ip_recursive on, если в цепочке несколько прокси и нужно брать первый адрес слева.
# http {}
log_format main_realip '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'xff="$http_x_forwarded_for" realip="$realip_remote_addr"';
set_real_ip_from 10.0.0.0/8; # адреса внутренних балансировщиков
set_real_ip_from 192.0.2.0/24; # пример подсети CDN/прокси
set_real_ip_from 2001:db8::/32; # IPv6 диапазоны, если есть
real_ip_header X-Forwarded-For;
real_ip_recursive on;
access_log /var/log/nginx/access_realip.log main_realip;
После такой настройки $realip_remote_addr станет реальным адресом клиента, а $remote_addr останется адресом последнего прокси. Для rate limiting, geoip, WAF и ACL используйте именно $realip_remote_addr.
JSON‑логи в nginx с real IP
log_format json_real escape=json '{"time":"$time_iso8601",'
'"remote":"$realip_remote_addr",'
'"proxy":"$remote_addr",'
'"xff":"$http_x_forwarded_for",'
'"method":"$request_method","uri":"$request_uri",'
'"status":$status,"bytes":$body_bytes_sent}';
access_log /var/log/nginx/access_json.log json_real;
nginx: настройка real IP через PROXY Protocol
Если балансировщик (или внешний прокси) умеет отдавать PROXY Protocol, включите его на бэкенде:
# сервер слушает порт с поддержкой PROXY Protocol
server {
listen 80 proxy_protocol;
# источник IP берём из PROXY Protocol
real_ip_header proxy_protocol;
# для логов
log_format main_proxy '$proxy_protocol_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'proxy="$remote_addr"';
access_log /var/log/nginx/access_proxy.log main_proxy;
location / {
proxy_pass http://127.0.0.1:8080;
}
}
В этом случае реальный адрес будет в $proxy_protocol_addr, а также подставится в $realip_remote_addr, если задан real_ip_header proxy_protocol. При использовании PROXY Protocol не требуется доверять заголовкам, но если вы параллельно принимаете XFF от CDN, оставьте $http_x_forwarded_for для диагностики.
Apache: X‑Forwarded‑For через mod_remoteip
Для Apache используйте модуль mod_remoteip, который заменяет адрес клиента на значение из доверённого заголовка.
# Загрузка модуля (если требуется вручную)
# LoadModule remoteip_module modules/mod_remoteip.so
# httpd.conf или внутри vhost
RemoteIPHeader X-Forwarded-For
RemoteIPInternalProxy 10.0.0.0/8
RemoteIPTrustedProxy 192.0.2.0/24
RemoteIPTrustedProxy 2001:db8::/32
# Логирование: %a станет реальным адресом клиента
LogFormat "%a %l %u %t \"%r\" %>s %b xff=\"%{X-Forwarded-For}i\"" combined_realip
CustomLog logs/access_realip.log combined_realip
Директивы RemoteIPInternalProxy и RemoteIPTrustedProxy ограничивают, от кого принимать заголовок — это критично для безопасности.
Apache: PROXY Protocol через mod_proxy_protocol
Чтобы Apache принимал PROXY Protocol от балансировщика, нужен модуль mod_proxy_protocol. С его помощью сервер читает адрес клиента из транспортного префикса, без участия заголовков.
# Загрузка модуля (если требуется вручную)
# LoadModule proxy_protocol_module modules/mod_proxy_protocol.so
# VirtualHost, принимающий соединения с PROXY Protocol
<VirtualHost *:80>
ProxyProtocol On
# Теперь %a уже равен реальному клиентскому IP
CustomLog logs/access_proxy.log "%a %l %u %t \"%r\" %>s %b"
</VirtualHost>
Если одновременно используется mod_remoteip для XFF, убедитесь, что порядок директив и допущения о доверенных источниках не конфликтуют. Для PROXY достаточно включить ProxyProtocol On на нужном VirtualHost.

Балансировщик: примеры для включения заголовков и PROXY
Ниже — иллюстративные примеры конфигурации популярного подхода. Суть в том, что фронт либо добавляет X-Forwarded-For, либо шлёт PROXY Protocol к бэкенду.
# Пример: фронт добавляет XFF
# (синтаксис условный; включайте опцию forwardfor у вашего балансировщика)
option forwardfor header X-Forwarded-For ifnone
# Пример: фронт шлёт PROXY Protocol на бэкенд
# (для конкретных балансировщиков это опция уровня сервера: send-proxy / proxy-protocol)
server app1 10.0.0.10:80 send-proxy
Выбирайте один основной механизм для определения real IP между балансировщиком и веб‑серверами. В сложных цепочках можно использовать оба: PROXY для надёжности и XFF для трассировки всей цепочки.
Логи: что именно писать
Практика показывает, что лучше логировать одновременно:
- Реальный IP (после восстановления) — для аналитики и безопасности.
- Адрес последнего прокси — для диагностики сетевого контура.
- Цепочку из XFF — для трассировки при сложных инцидентах.
Для nginx это может быть формат, показанный выше: realip, proxy и xff. Для Apache используйте %a (реальный после модулей), опционально — %h как исходный адрес соединения (адрес прокси), и %{X-Forwarded-For}i для заголовка.
Если вы ещё выбираете стек, посмотрите Nginx vs Apache: что выбрать в 2025, а для внедрения базовой защиты — руководство по security headers для Nginx и Apache.
Безопасность: типовые ошибки
- Доверять всем подряд. Нельзя ставить
set_real_ip_from 0.0.0.0/0или аналог в Apache: любой клиент сможет подделать IP через XFF. - Забыть IPv6 диапазоны доверенных прокси, из‑за чего часть трафика будет считаться «недоверенной» и адрес не восстановится.
- Смешивать PROXY и XFF без явной приоритизации. Если включили PROXY между балансировщиком и nginx, используйте
real_ip_header proxy_protocolкак источник правды. - Конфигурировать
limit_req_zoneв nginx доreal_ip_header: зона схватит$binary_remote_addrот прокси. Сначала определяйтеreal_ip_header, затем объявляйте лимиты. - Доверять клиентским нестандартным заголовкам. Используйте стандартные XFF/Forwarded, переписанные вашим прокси, а не произвольные заголовки.
Проверка и отладка
Начните с локального запроса, подставив XFF, и проверьте логи.
curl -H "X-Forwarded-For: 203.0.113.50" http://example.test/
Во временной конфигурации nginx можно добавить отладочный эндпоинт, чтобы убедиться, какие значения видит сервер:
server {
listen 8081;
location = /debug-ip {
default_type text/plain;
return 200 "remote_addr=$remote_addr\nrealip=$realip_remote_addr\nxff=$http_x_forwarded_for\nproxyproto=$proxy_protocol_addr\n";
}
}
Если используете PROXY Protocol, проверьте, что фронт действительно его шлёт, а бэкенд слушает сокет с включённым режимом. Признак ошибки — 400/408 на старте соединения или «мусор» в начале запроса при попытке читать HTTP без парсинга PROXY.
Где ещё влияет real IP
- Rate limiting и анти‑боты: лимиты должны учитывать реальный IP, иначе вы лимитируете балансировщик.
- WAF/IDS: решения на уровне веб‑сервера или приложений должны видеть IP клиента для корректных правил.
- GeoIP и персонализация: база геолокации должна получать
$realip_remote_addrили эквивалент. - fail2ban и алерты: регулярки в фильтрах должны соответствовать новым форматам логов (реальный IP в отдельном поле).
XFF vs Forwarded: что выбрать
X-Forwarded-For поддерживается везде и прост. Forwarded стандартизован и лучше описывает IPv6/протокол/порт, но поддержка в парсерах менее единообразна. На практике используйте XFF как базу совместимости, а Forwarded — дополнительно при наличии.
Чек‑лист внедрения
- Определите границы доверия: список IP/подсетей ваших прокси и CDN.
- Выберите механизм: XFF или PROXY между балансировщиком и веб‑серверами.
- Настройте nginx/Apache на восстановление real IP (директивы выше).
- Обновите форматы логов: добавьте реальный IP, адрес прокси, XFF.
- Проверьте rate limiting, WAF, аналитики — чтобы они брали реальный IP.
- Покройте IPv6 и пересмотрите конфиг при изменении периметра.
Итого
Чтобы стабильно получать real IP за прокси и CDN, выбирайте один проверенный источник правды: транспортный PROXY Protocol либо правильно оформленный и переписываемый на фронте X-Forwarded-For. В nginx и Apache это решается несколькими директивами, но ключевые моменты — доверять только своим прокси, корректно логировать и учитывать real IP во всех механизмах безопасности и производительности. После внедрения вы получите чистые логи, предсказуемое поведение лимитов и корректную аналитику.


