В логах сайта один IP вместо посетителей: как настроить real IP за прокси
После подключения CDN или балансировщика в журнале сайта может остаться один IP вместо адресов посетителей. Это не обязательно ошибка: Nginx видит непосредственного участника соединения, которым стал прокси. Ошибка возникает, когда приложение считает этот адрес посетителем либо без проверки доверяет любому присланному заголовку.
Задача «nginx реальный ip за прокси» решается через явную цепочку доверия. Нужно установить, кто соединяется с сервером, какой узел формирует сведения о клиенте и как они проходят до приложения. Правильная настройка позволяет различать сетевой источник и определённый адрес клиента, сохраняя доказательства в журнале. Простая замена поля логирования на X-Forwarded-For такой проверки не выполняет.
1. Нарисуйте фактический путь запроса
Начните с маршрута одного запроса: посетитель, публичный прокси или CDN, внутренний балансировщик, Nginx, приложение. Для каждого перехода укажите адрес отправителя, вид соединения и способ передачи клиентского IP. Не ограничивайтесь схемой из проекта: проверьте текущий путь домена, служебных поддоменов и разрешённых обращений к origin.
Для дальнейшего разбора используем учебную цепочку. Клиент имеет адрес 198.51.100.7, публичный прокси — 203.0.113.10, внутренний балансировщик — 10.0.0.10. Последний открывает соединение к Nginx. Это условные адреса; они не являются списком Cloudflare и не предназначены для переноса в рабочую конфигурацию.
Если между CDN и Nginx добавился балансировщик, непосредственным peer для Nginx станет именно он. Добавление только сетей CDN в доверенный список может не изменить результат. Но автоматическое доверие всей внутренней сети тоже неверно: нужно понимать, какие узлы действительно вправе сообщать клиентский адрес.
Сохраните также ожидаемые альтернативные маршруты: проверку доступности, административный доступ и внутренние обращения. Для каждого заранее решите, должен ли он проходить через прокси или обращаться напрямую. Неожиданный обход цепочки должен обнаруживаться при приёмке.
2. Доверяйте отправителю соединения, а не названию заголовка
Модуль ngx_http_realip_module заменяет клиентский адрес по выбранному источнику, если исходный адрес соответствует доверенным. Директива set_real_ip_from задаёт такие адреса или сети. Наличие строки X-Forwarded-For либо CF-Connecting-IP само по себе не делает запрос доверенным.
Посетитель способен отправить собственный HTTP-заголовок. Поэтому доверенный прокси должен формировать сведения по согласованному правилу, а Nginx — принимать их только от предусмотренного источника. Если прямой запрос неизвестного клиента содержит чужой IP, это входные данные клиента, а не подтверждённый адрес.
Не используйте 0.0.0.0/0 или ::/0 как доверенные сети ради быстрого исправления логов. Такой выбор превращает любого отправителя в источник доверенной информации. Слишком широкая внутренняя подсеть создаёт похожую проблему, если в ней работают посторонние приложения или доступные пользователям узлы.
Определённый IP остаётся адресом на границе доверенной цепочки. Это может быть выход VPN, корпоративного шлюза или общего NAT. Он не идентифицирует человека и не заменяет авторизацию. Настройка real IP также не позволяет достоверно восстановить адрес до неизвестного внешнего прокси, которому ваша система не доверяет.
3. Разберите X-Forwarded-For справа налево
В корректно формируемой цепочке X-Forwarded-For более ранние адреса находятся слева, более поздние прокси — справа. Непосредственный peer проверяется отдельно: он обычно не является последним элементом присланного им заголовка. В нашем примере peer равен 10.0.0.10, а заголовок содержит:
X-Forwarded-For: 198.51.100.7, 203.0.113.10
Если исходный peer доверенный, при real_ip_recursive off выбирается последний адрес заголовка. При on поиск идёт с правого края до последнего недоверенного адреса, пропуская доверенные прокси. Это не правило «всегда брать первый IP». Результат зависит от списка доверенных участников.
| Доверенные адреса | Режим | Результат в примере |
|---|---|---|
| 10.0.0.10 и 203.0.113.10 | recursive off | 203.0.113.10 — последний адрес заголовка |
| 10.0.0.10 и 203.0.113.10 | recursive on | 198.51.100.7 — последний недоверенный адрес |
| Только 10.0.0.10 | recursive on | 203.0.113.10 — поиск останавливается на нём |
| Peer 10.0.0.10 не доверен | Любой из этих режимов | Адрес соединения не заменяется по этому заголовку |
Не добавляйте адрес посетителя в доверенные ради прохождения цепочки до самого левого элемента. Доверенный список описывает инфраструктуру, уполномоченную сообщать IP, а не желаемый результат. Если цепочка теряется на промежуточном узле, сначала исправьте её формирование там.
Рассмотрим подставленный префикс в варианте, где прокси сохраняют входящий XFF и дополняют его проверенными адресами. Получилась строка 192.0.2.99, 198.51.100.7, 203.0.113.10, причём первое значение прислал клиент. При доверенном peer 10.0.0.10 и доверенном прокси 203.0.113.10 рекурсивный поиск остановится на 198.51.100.7. До выдуманного левого адреса он не дойдёт. Это объясняет, почему разбор начинается с ближайшей проверенной границы, а не с визуально первого элемента.
4. Примените пример только к известной топологии
Для описанной учебной цепочки настройки в существующем блоке server origin-сервера могут выглядеть следующим образом. Оба прокси должны находиться под вашим контролем и передавать адреса по проверенному правилу:
set_real_ip_from 10.0.0.10;
set_real_ip_from 203.0.113.10;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
Это не полная конфигурация сервера. Перед применением заменяют учебные адреса подтверждёнными адресами инфраструктуры, проверяют наличие realip-модуля в сборке и область наследования настроек. Не распространяйте правило на все виртуальные хосты, если у них разные пути доставки трафика.
Публичный пограничный прокси должен очищать или корректно формировать входящую цепочку согласно выбранной архитектуре. Внутренний балансировщик обязан передавать её согласованно. Если он просто сохраняет произвольный заголовок, полученный от кого угодно, доверие его адресу недостаточно для достоверного определения клиента.
На управляемом хостинге передайте исполнителю схему переходов и ожидаемый результат таблицы. Просьба «включить XFF» не определяет ни доверенный источник, ни алгоритм выбора адреса. Запрос должен содержать конкретный домен, известные прокси и проверку прямого запроса с поддельным заголовком.
5. Сохраняйте исходный peer и определённый client
После обработки real IP значение $remote_addr отражает выбранный клиентский адрес, а $realip_remote_addr сохраняет исходный адрес соединения. На время приёмки полезно видеть оба значения. Сырой заголовок храните отдельным полем: он помогает разбору, но не должен выглядеть как уже проверенный IP.
Ниже пример ограниченного диагностического формата. По документации модуля журналирования log_format задаётся в контексте http, а escape=json экранирует значения для строк JSON:
log_format realip_audit escape=json
'{"peer":"$realip_remote_addr","client":"$remote_addr",'
'"xff":"$http_x_forwarded_for","cf":"$http_cf_connecting_ip"}';
Подключение выбранного формата через access_log выполняют для нужного сервера или маршрута в существующей конфигурации. Проверьте, какой журнал действительно получает запрос: внутренний переход на другую обработку может привести к записи по другой настройке.
Не добавляйте в диагностический формат cookies, токены и полные тела запросов. Ограничьте доступ к журналу и период расширенного сбора. Для сравнения достаточно адресов, времени контрольного обращения и известного сценария. Различие полей peer и client ожидаемо для проксированного запроса; их совпадение требует оценки маршрута, а не автоматического объявления ошибки.
6. Почему в логах IP Cloudflare и какой заголовок выбирать
Когда Cloudflare проксирует HTTP-трафик, origin получает соединение от инфраструктуры CDN. Для обычного запроса без подзапроса Worker заголовок CF-Connecting-IP передаёт адрес клиента, подключившегося к Cloudflare. Семантика и исключения описаны в официальной документации HTTP-заголовков Cloudflare.
В такой схеме можно выбирать real_ip_header CF-Connecting-IP, предварительно настроив доверенный путь. Инструкция Cloudflare для восстановления IP использует стандартный realip-модуль Nginx. Отдельный заголовок содержит одно значение и не требует разбирать XFF как произвольную цепочку.
Однако эта строка не подходит автоматически для любого промежуточного балансировщика. Если Nginx видит peer внутреннего прокси, тот должен принимать соответствующий трафик только по предусмотренному маршруту и корректно передавать или заново формировать поле. Нельзя доверять ему как ретранслятору CF-Connecting-IP, если он принимает этот заголовок от произвольных прямых клиентов.
Проверьте также, что нужный домен действительно проходит через HTTP-прокси CDN. Смешанная схема, где часть запросов идёт напрямую, требует отдельных ожидаемых результатов. Наличие DNS-записи у провайдера не следует принимать за доказательство прохождения каждого HTTP-запроса через него.
7. Согласуйте актуальные сети CDN и доступ к origin
Для Cloudflare используйте официальный текущий список IPv4- и IPv6-диапазонов. Не копируйте в постоянную инструкцию набор адресов из случайной публикации. Список доверия должен обновляться управляемо: источник, дата проверки, различия, проверка синтаксиса и применение с возможностью возврата.
Отдельно ограничьте сетевой доступ к origin согласно архитектуре. Рекомендации Cloudflare по IP-адресам предусматривают разрешение нужных источников и блокирование обходного трафика. Список set_real_ip_from определяет доверие к сведениям об адресе, но сам по себе не является правилом firewall, запрещающим соединение.
Учтите служебные исключения: доверенного партнёра, внутреннюю интеграцию, административный путь. Исключение должно иметь понятное назначение и отдельную обработку клиентского адреса. Не расширяйте доверенную сеть до всего Интернета из-за одной проверки, которая перестала проходить.
Диапазон Cloudflare подтверждает принадлежность peer сети провайдера, но не вашей конкретной зоне. Для более строгого подтверждения Cloudflare описывает Authenticated Origin Pulls на уровне зоны или hostname с собственным сертификатом. Общий сертификат глобального AOP тоже не подтверждает именно ваш аккаунт. Выбор такого механизма — отдельное согласованное решение, а не дополнительная строка real IP.
8. Учтите Pseudo IPv4 и подзапросы Workers
Если ожидаемый IPv6 превратился в необычный IPv4, проверьте настройку Pseudo IPv4. В режиме Overwrite Headers Cloudflare заменяет CF-Connecting-IP и X-Forwarded-For синтетическим IPv4, сохраняя исходный IPv6 в CF-Connecting-IPv6. Это иной смысл значения, а не доказательство неправильного парсинга Nginx.
Для Workers правила зависят от назначения подзапроса. В пределах одной зоны CF-Connecting-IP отражает X-Real-IP, который может изменяться кодом Worker. Для подзапросов между зонами Cloudflare предусмотрен специальный адрес Worker; для назначения вне Cloudflare клиентские IP-заголовки также могут контролироваться кодом Worker. Поэтому нельзя обещать исходный адрес посетителя для любого запроса только по имени поля.
Если Workers присутствуют в вашем маршруте, проверьте их код и отдельные контрольные сценарии. Заранее решите, что приложение считает клиентским адресом в каждом варианте. Не смешивайте синтетический IPv4, исходный IPv6 и служебный адрес подзапроса в одном отчёте без указания их назначения.
Для обычного сайта без этих возможностей достаточно отметить их отсутствие в схеме. Не включайте дополнительные преобразования ради одинакового внешнего вида адресов: сначала убедитесь, что приложение и журналы корректно принимают IPv6.
9. Согласуйте результат с приложением
Исправленный журнал Nginx не доказывает, что CMS или backend использует тот же адрес. Приложение может самостоятельно читать XFF, доверять другому заголовку или продолжать видеть адрес внутреннего сервера. Сравните результат в Nginx и в закрытой диагностике приложения для одного контрольного запроса.
Выберите понятную модель: либо последний доверенный узел передаёт приложению одно нормализованное значение, либо приложение само разбирает согласованную цепочку доверенных прокси. Не оставляйте два независимых механизма, один из которых снова принимает произвольный самый левый адрес. Приложение должно доверять соответствующему полю только от своего известного прокси, а прямой доступ к backend необходимо контролировать отдельно.
В документации proxy-модуля Nginx переменная $proxy_add_x_forwarded_for добавляет $remote_addr к полученному XFF. После real IP эта переменная уже может содержать определённого клиента. Бездумное повторное добавление способно дать дубли и не создаёт дополнительного доказательства доверия.
Если передаёте нормализованное поле, доверенный узел должен перезаписывать соответствующий входящий заголовок, а не сохранять клиентское значение как равноправный источник. Конкретное имя поля и настройку trusted proxies выбирают по документации приложения. Проверяйте также ограничения частоты запросов и правила доступа, которые используют IP: после исправления изменится их фактический ключ.
10. Не путайте PROXY protocol с HTTP-заголовком
Некоторые балансировщики передают адреса через PROXY protocol. Это служебные данные перед полезной нагрузкой соединения, а не заголовок, который браузер добавляет в HTTP-запрос. Настройка real_ip_header proxy_protocol предполагает соответствующий режим принимающего listener.
В официальной инструкции Nginx по PROXY protocol приём включается параметром proxy_protocol у listen. Передающий балансировщик и принимающая сторона должны поддерживать согласованный режим. Не включайте его на обычном публичном listener только потому, что в логах виден IP прокси: обычный HTTP- или TLS-клиент не отправляет ожидаемый префикс.
Доступ к такому listener ограничивают доверенным передатчиком, а замену адреса связывают с его известным peer. Сам протокол не делает произвольного отправителя доверенным. Проверка должна охватывать и нормальный путь балансировщика, и предусмотренное поведение для неподходящего прямого подключения.
Если инфраструктура уже использует HTTP-заголовки и не требует PROXY protocol, не добавляйте второй механизм без архитектурной причины. Его включение меняет требования к соединению и может нарушить проверки доступности, если их маршрут не согласован.
11. Проверьте нормальный и поддельный запросы
Испытания проводят только на своём согласованном стенде или сайте, безопасными запросами без изменения данных. Используйте известный исходящий IP тестового клиента и контролируемый маршрут. Сопоставляйте peer, выбранный client и результат приложения. Одного красивого IP в журнале недостаточно.
| Проверка | Ожидаемый результат |
|---|---|
| Обычный запрос через все доверенные прокси | Peer соответствует последнему узлу; client соответствует ожидаемой границе цепочки |
| Клиент подделывает XFF или другой выбранный IP-заголовок через штатный путь | Подставленный адрес не становится доверенным результатом; прокси формирует сведения по своему правилу |
| Прямой запрос к origin с поддельным заголовком | Запрос блокируется политикой доступа либо сохраняет недоверенный peer как клиентский адрес |
| Предусмотренный служебный прямой маршрут | Работает по отдельному правилу и не даёт произвольно подменить IP |
| IPv6 и используемые Worker-сценарии | Результат соответствует документированной семантике выбранного пути |
Не открывайте публичный доступ к origin ради отрицательного теста. Если firewall должен отклонять соединение, подтверждение этого отказа уже является нужным результатом на сетевом уровне; проверку обработки заголовка можно выполнить в изолированном стенде. Для HTTPS сохраняйте правильное имя сайта и проверку сертификата, чтобы тест действительно относился к нужному виртуальному хосту.
Убедитесь, что контрольный запрос действительно дошёл до origin. Ответ из кеша CDN не создаёт новое обращение к Nginx и не проверяет его текущую конфигурацию. Для испытаний выбирают согласованный безопасный маршрут с подходящим режимом кеширования, а приход подтверждают свежей записью журнала. Не отключайте кеш всего сайта ради одного опыта. Также не считайте адрес, показанный клиентским интерфейсом, доказательством того, какой IP использовал backend при обработке запроса.
12. Примените настройку и назначьте владельца списка доверия
Перед применением сохраните прежнюю конфигурацию и диагностический результат. Проверьте синтаксис, выполните штатное перечитывание и повторите контрольные сценарии на всех обслуживающих экземплярах. Ошибка одного узла может проявляться только для части запросов, поэтому единичная успешная проверка не закрывает приёмку распределённого сайта.
Заранее определите признаки неудачного внедрения: неожиданный отказ административного доступа, массовое срабатывание ограничений или принятие подставленного адреса. Сохраните разрешённый независимый административный канал для восстановления конфигурации. Если результат отличается от согласованного, остановите распространение изменений на остальные узлы и вернитесь к последнему проверенному состоянию. Сам факт доступности главной страницы не отменяет ошибку определения клиента, если от неё зависят правила доступа или аудит операций.
Назначьте ответственного за актуальность сетей, изменение топологии и настройки приложения. Добавление балансировщика, смена CDN или подключение Worker должны запускать повторную проверку цепочки. Обновление списка через автоматизацию тоже нуждается в валидации: пустой ответ источника или ошибочный формат нельзя без проверки применять как рабочую конфигурацию.
В итоговой записи сохраните схему переходов, разрешённые peer, выбранный заголовок, режим рекурсии и ожидаемые результаты отрицательных тестов. Отдельно укажите, какое поле приложения используется для ограничений и аудита. После приёмки сократите временное журналирование до необходимого объёма.
Подключите SiteGuard для внешнего контроля HTTP-доступности сайта после изменения прокси. Достоверность определения IP проверяет ваша команда.