Доверенные прокси
Когда OxPHP работает за обратным прокси (Kubernetes Ingress, Cloudflare, AWS ALB, nginx), каждый запрос приходит с IP-адреса прокси. Без настройки доверенных прокси ограничение частоты запросов, журналирование доступа и $_SERVER['REMOTE_ADDR'] — все видят IP прокси вместо реального клиента.
Конфигурация
# Comma-separated CIDR list
TRUSTED_PROXIES="10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"
# Shorthand: all RFC-1918 + loopback + link-local (IPv4 and IPv6)
TRUSTED_PROXIES="private"
# Keywords and CIDRs combine in one list
TRUSTED_PROXIES="private,cloudflare,100.64.0.0/10"Каждая запись в списке через запятую — это CIDR, голый IP-адрес (отдельный хост) или одно из ключевых слов private и cloudflare, в любом регистре. Запись, не являющаяся ничем из этого, приводит к ошибке при запуске.
Если переменная не задана, OxPHP игнорирует все заголовки перенаправления. Это безопасное значение по умолчанию.
Как это работает
Когда запрос приходит с доверенного IP, OxPHP проверяет заголовки перенаправления в порядке приоритета:
Forwarded(RFC 7239) — стандартизированный заголовокX-Forwarded-For/X-Forwarded-Proto/X-Forwarded-Host/X-Forwarded-Port— де-факто резервный вариант
Если присутствует заголовок Forwarded, заголовки X-Forwarded-* игнорируются.
Извлечение IP-адреса клиента
OxPHP использует алгоритм rightmost-non-trusted (крайний правый недоверенный) — тот же подход, что и в nginx (real_ip_recursive on), Caddy, Traefik и Apache:
X-Forwarded-For: 203.0.113.50, 172.16.1.1, 10.0.0.5
TCP peer: 10.0.0.1 (trusted)
Walk right-to-left:
10.0.0.5 → trusted → skip
172.16.1.1 → trusted → skip
203.0.113.50 → NOT trusted → client IPЭто предотвращает подмену через добавленные слева значения. Атакующий может добавить поддельные IP слева, но крайний правый недоверенный IP был установлен последним доверенным прокси в цепочке.
Запись может нести порт: 203.0.113.50:4711 или [2001:db8::1]:4711 — в такой форме некоторые балансировщики нагрузки пишут адрес в X-Forwarded-For (среди них Azure Application Gateway). Когда адрес клиента берётся из такой записи, её порт становится REMOTE_PORT.
Обход останавливается на записи без адреса или с адресом, который не разбирается как IP-адрес (пустой элемент, unknown, обфусцированный идентификатор RFC 7239), а также на строке заголовка, которую OxPHP не может прочитать как текст (например, содержащей не-ASCII байт). Ничто левее этой точки не используется, поскольку эта часть цепочки может оказаться лишь тем, что прислал клиент. Адрес клиента — крайняя правая недоверенная запись правее этой точки. Если все записи там доверенные, это самая левая из них; если там нет ни одной записи, адрес не перезаписывается и остаётся адресом подключившегося узла, включая порт.
X-Forwarded-For: 198.51.100.9, unknown, 10.0.0.5
TCP peer: 10.0.0.1 (trusted)
Walk right-to-left:
10.0.0.5 → trusted → skip
unknown → does not parse → stop
198.51.100.9 → never reached
Client IP: 10.0.0.5 (every entry right of the stop is trusted → the leftmost of them)С ключевым словом cloudflare запрос, цепочка перенаправления которого показывает, что он пришёл с пограничного узла Cloudflare, вместо этого берёт адрес клиента из заголовка CF-Connecting-IP. См. За CDN и балансировщиком нагрузки платформы.
Что меняется
Когда TRUSTED_PROXIES настроен и подключающийся IP является доверенным:
| Компонент | Без доверенных прокси | С доверенными прокси |
|---|---|---|
$_SERVER['REMOTE_ADDR'] |
IP прокси | Реальный IP клиента; IP прокси, когда заголовки перенаправления не дают адреса |
$_SERVER['REMOTE_PORT'] |
Исходный порт прокси | Порт клиента из записи ip:port в Forwarded или X-Forwarded-For, иначе 0; порт узла, когда адрес не перезаписывается |
$_SERVER['HTTPS'] |
На основе TLS-конфигурации OxPHP | Из Forwarded: proto= или X-Forwarded-Proto |
$_SERVER['REQUEST_SCHEME'] |
http или https из TLS |
Из перенаправленного протокола |
$_SERVER['SERVER_NAME'] |
Из заголовка Host |
Из Forwarded: host= или X-Forwarded-Host |
$_SERVER['SERVER_PORT'] |
Из заголовка Host |
Из X-Forwarded-Port, иначе порт из X-Forwarded-Host / Forwarded: host=, иначе 443/80 |
| Ограничение частоты запросов | По IP прокси | По IP клиента |
| Журнал доступа | IP прокси | Реальный IP клиента |
Когда адрес клиента перезаписывается, REMOTE_PORT — это исходный порт клиента, если запись, из которой взят адрес, его несёт (узел RFC 7239 Forwarded: for="ip:port" или запись ip:port в X-Forwarded-For), и 0 в противном случае. Порт невозможно восстановить, поэтому вместо угадывания он обнуляется. У адреса, взятого из CF-Connecting-IP, порта тоже нет, поэтому и тогда REMOTE_PORT равен 0. Когда заголовки не дают адреса клиента и адрес не перезаписывается, как описано в разделе Извлечение IP-адреса клиента, REMOTE_PORT остаётся портом подключившегося узла.
Сети private
Сокращение private включает:
| Сеть | Описание |
|---|---|
10.0.0.0/8 |
Частная сеть класса A |
172.16.0.0/12 |
Частная сеть класса B |
192.168.0.0/16 |
Частная сеть класса C |
127.0.0.0/8 |
Loopback |
169.254.0.0/16 |
Link-local |
::1/128 |
IPv6 loopback |
fc00::/7 |
IPv6 unique local |
fe80::/10 |
IPv6 link-local |
Общее адресное пространство (100.64.0.0/10, RFC 6598) в private не входит. Если ваш балансировщик нагрузки подключается из него, укажите его рядом с ключевым словом: TRUSTED_PROXIES="private,100.64.0.0/10".
Сети cloudflare
Ключевое слово cloudflare добавляет пограничные диапазоны, которые Cloudflare публикует на cloudflare.com/ips: 15 диапазонов IPv4 и 7 диапазонов IPv6 по состоянию на 2026-10-05. Список вкомпилирован в OxPHP и меняется только с выпуском OxPHP. Если список Cloudflare изменится раньше:
- Новый диапазон. Укажите его рядом с ключевым словом:
TRUSTED_PROXIES="private,cloudflare,<new range>". Указанный диапазон считается одним из ваших собственных прокси, а не пограничным узлом Cloudflare, поэтому запросы через него получают адрес по правилу rightmost-non-trusted, а не изCF-Connecting-IP, пока выпуск не добавит этот диапазон в ключевое слово. - Отозванный диапазон. Его нельзя вычесть из ключевого слова. Чтобы исключить его раньше, чем это сделает выпуск OxPHP, перечислите диапазоны, которым вы по-прежнему доверяете, в виде CIDR вместо ключевого слова. Это также означает отказ от чтения
CF-Connecting-IP, которое привязано к ключевому слову.
За CDN и балансировщиком нагрузки платформы
Распространённая схема развёртывания ставит CDN перед балансировщиком нагрузки хостинговой платформы:
client → Cloudflare → load balancer → OxPHPПри TRUSTED_PROXIES=private доверенным считается только балансировщик нагрузки. Крайним правым недоверенным адресом в X-Forwarded-For тогда оказывается пограничный узел Cloudflare, подключившийся к балансировщику, и каждый запрос выглядит пришедшим от Cloudflare — в REMOTE_ADDR, журнале доступа, ограничении частоты запросов и атрибуте спана client.address. Доверяйте обоим звеньям:
TRUSTED_PROXIES="private,cloudflare"Если балансировщик нагрузки подключается с адреса вне диапазонов private, укажите и его диапазон: TRUSTED_PROXIES="private,100.64.0.0/10,cloudflare".
С ключевым словом cloudflare OxPHP определяет адрес клиента в три шага:
- 1Пройти мимо собственных прокси. Начиная с подключившегося узла, OxPHP идёт влево по цепочке перенаправления (
Forwarded, если он есть, иначеX-Forwarded-For) мимо ваших собственных прокси: диапазоновprivateи указанных вами CIDR. Адрес внутри диапазонов Cloudflare считается адресом Cloudflare, даже если вы его указали. - 2Найти звено Cloudflare. Если первое звено, не являющееся одним из ваших собственных прокси, — адрес Cloudflare, значит, запрос пришёл через Cloudflare, и адрес клиента берётся из заголовка
CF-Connecting-IP. Заголовок должен встречаться ровно один раз и содержать корректный IP-адрес. - 3Иначе — rightmost-non-trusted. Если запрос пришёл не через Cloudflare или
CF-Connecting-IPотсутствует, повторяется или некорректен, применяется правило rightmost-non-trusted, при этом диапазоны Cloudflare считаются доверенными.
Звено, на котором останавливается обход, — это подключившийся узел или адрес, записанный одним из ваших собственных прокси. Поэтому клиент, обращающийся к балансировщику нагрузки напрямую, не может заставить OxPHP прочитать CF-Connecting-IP, который он прислал сам: балансировщик записывает туда собственный адрес клиента, а до адреса Cloudflare, который клиент вписал левее, обход никогда не доходит. Как и правило rightmost-non-trusted, это опирается на то, что ваши прокси поддерживают заголовок, который читает OxPHP, — дописывают адрес подключившегося к ним узла, а не пропускают значение клиента без изменений.
Ваши прокси должны поддерживать и Forwarded. OxPHP читает его в приоритете перед X-Forwarded-For, а Cloudflare передаёт его дальше в том виде, в каком его прислал посетитель, поскольку это не один из заголовков, которые устанавливает Cloudflare. Поэтому балансировщик нагрузки, поддерживающий только X-Forwarded-For, должен удалять Forwarded из запроса. Если он пропускает этот заголовок, любой клиент — через Cloudflare или нет — выбирает адрес, который видит OxPHP, с ключевым словом cloudflare или без него.
CF-Connecting-IP не читается и тогда, когда обход останавливается, не дойдя до адреса Cloudflare, — на записи без адреса или с неразбираемым адресом либо на строке, которую OxPHP не может прочитать как текст, как описано в разделе Извлечение IP-адреса клиента. На запись, которую один из ваших прокси помещает на отдельную строку, не влияет нечитаемая строка, присланная клиентом перед ней. Запись, которую прокси пишет в ту же строку, что и текст, присланный клиентом, теряется вместе с этой строкой: обход останавливается на ней, и адрес берётся из того, что ваши прокси записали правее этой строки, или остаётся адресом подключившегося узла — но никогда не из того, что прислал клиент.
Cloudflare дописывает в X-Forwarded-For адрес того, кто к нему подключился, поэтому для обычного посетителя оба заголовка называют одного и того же клиента. Они могут различаться для запросов, которые Cloudflare Workers отправляют на ваш исходный сервер: такие запросы тоже уходят с адресов Cloudflare, какому бы аккаунту ни принадлежал Worker. Справочник HTTP-заголовков Cloudflare не говорит, что такой запрос может нести в X-Forwarded-For. Про CF-Connecting-IP говорит: Worker может выбрать значение только для запроса к хосту в собственной зоне; для хоста в другой зоне Cloudflare заголовок содержит фиксированный адрес 2a06:98c0:3600::103, а для хоста не на Cloudflare — адрес клиента, который Worker изменить не может.
Доверие к диапазонам Cloudflare всё равно означает доверие ко всему, что выходит из сети Cloudflare, а не только к вашей зоне. Если адрес клиента влияет на решения о безопасности, сделайте так, чтобы исходный сервер принимал трафик только из вашей зоны, — теми средствами, которые предлагает ваша платформа.
Безопасность
- Безопасное значение по умолчанию — без
TRUSTED_PROXIESзаголовки перенаправления не обрабатываются - Валидация CIDR — некорректные значения в
TRUSTED_PROXIESприводят к ошибке при запуске - Устойчивость к подмене — алгоритм rightmost-non-trusted игнорирует добавленные атакующим значения
- Заголовки перенаправления в запросах с недоверенных IP игнорируются полностью
Промпты для вашего агента
Эти промпты рассчитаны на агента с подключённым MCP-сервером документации OxPHP — нужные страницы он прочитает сам.
Настрой доверенные прокси для моего развёртывания OxPHP. Прочитай страницу `security/trusted-proxies` через MCP-сервер документации OxPHP.
Определи по моим инфраструктурным файлам, что стоит перед приложением, затем дай точную конфигурацию и скажи мне, что впускает каждое значение и что клиент смог бы подделать, ошибись я в нём.Смотрите также
- Ограничение частоты запросов — ограничение частоты по IP использует определённый IP клиента
- Журналирование доступа — поле
remote_ipпоказывает определённый IP клиента - Справочник по конфигурации — все переменные окружения