Выберите продукт

Nginx real IP за CDN и прокси: real_ip_header и set_real_ip_from без ошибок

Если сайт стоит за CDN или обратным прокси, Nginx видит IP прокси вместо клиента. Разберём real_ip_header и set_real_ip_from, как не попасть на X-Forwarded-For spoofing, примеры для Cloudflare и быстрые тесты по логам.
Nginx real IP за CDN и прокси: real_ip_header и set_real_ip_from без ошибок

Зачем вообще настраивать real IP в Nginx

Когда перед вашим Nginx стоит CDN, балансировщик или обратный прокси, то в $remote_addr по умолчанию будет IP ближайшего прокси (например, узла CDN), а не реальный адрес посетителя. Это ломает сразу несколько вещей:

  • логи доступа: аналитика и расследования инцидентов становятся неточными;
  • ограничения по IP: limit_req, limit_conn, allow/deny начинают работать «по прокси»;
  • приложения: антифрод, гео, персонализация, аудит действий пользователя «видят» не того клиента.

Модуль ngx_http_realip_module решает задачу: он меняет «видимый» адрес клиента на значение из доверенного заголовка, но только если запрос пришёл от доверенного прокси. В этом условии и спрятаны типовые ошибки и риски.

Ключевая идея: доверяйте заголовкам только от доверенных прокси

CDN и reverse proxy обычно добавляют заголовок с исходным IP (или цепочкой прокси). Чаще всего это X-Forwarded-For, иногда — провайдерский заголовок (например, у Cloudflare популярен CF-Connecting-IP).

Проблема: заголовки вроде X-Forwarded-For подделываются. Если Nginx начнёт брать IP из заголовка для любого запроса, злоумышленник сможет подменить адрес клиента. Это классический сценарий x-forwarded-for spoofing.

Если не ограничить доверенные источники через set_real_ip_from (или поставить слишком широкие сети вроде 0.0.0.0/0), вы фактически говорите: «верю любому заголовку от любого клиента».

Директивы real IP: что делает каждая

set_real_ip_from

Список доверенных источников (IP/подсети), которые имеют право «сообщать» вам реальный IP клиента через заголовки. Это должны быть адреса ваших прокси/CDN/LB. Если запрос пришёл не от них — заголовки игнорируются, а $remote_addr остаётся прежним.

real_ip_header

Указывает, из какого заголовка брать реальный IP. Частые варианты:

  • X-Forwarded-For — стандартная цепочка прокси;
  • X-Real-IP — часто выставляют Nginx/балансировщики;
  • CF-Connecting-IP — Cloudflare.

real_ip_recursive

Определяет, как обрабатывать цепочку адресов в X-Forwarded-For (когда прокси несколько). При real_ip_recursive on; Nginx будет «раскручивать» список и искать реальный клиентский IP, пропуская доверенные прокси в цепочке. Это полезно в многослойных схемах: CDN → L7-балансировщик → Nginx.

Если ваш проект размещён на VDS, real IP почти всегда нужен для корректных лимитов и разборов инцидентов: на выделенной машине вы сами отвечаете за сетевую модель (прокси, VPN, балансировщики) и качество логов.

Схема цепочки прокси: клиент, CDN, балансировщик и origin с заголовками реального IP

Минимальная безопасная конфигурация: один доверенный прокси

Если перед вами один обратный прокси (например, локальный балансировщик внутри сети), самый простой вариант:

http {
    set_real_ip_from 10.0.0.0/8;
    real_ip_header X-Forwarded-For;
    real_ip_recursive on;

    # ... остальная конфигурация
}

Здесь доверяем заголовкам только от подсети 10.0.0.0/8. Если клиент с интернета попробует прислать свой X-Forwarded-For — Nginx его проигнорирует, потому что источник не из доверенной подсети.

Сценарий «client IP behind proxy»: CDN → ваш Nginx

Когда вы ставите CDN (в том числе в режиме reverse proxy), ваш сервер видит IP узлов CDN. Чтобы восстановить клиентский адрес, нужно:

  • выбрать заголовок с клиентским IP, который гарантированно выставляет CDN;
  • внести в set_real_ip_from все подсети CDN;
  • включить real_ip_recursive, если между CDN и Nginx есть дополнительные прокси.

Если CDN используется ради скорости, посмотрите также материал про связку CDN и WordPress на виртуальном хостинге: как ускорить WordPress на shared-хостинге с CDN.

FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

Cloudflare real IP: как настроить правильно

Для Cloudflare часто выбирают заголовок CF-Connecting-IP: в нём Cloudflare передаёт один адрес клиента (не цепочку). Тогда обработка проще и меньше шансов ошибиться с рекурсией.

Базовый шаблон для Nginx (диапазоны ниже — пример; в рабочей конфигурации используйте актуальные подсети провайдера):

http {
    real_ip_header CF-Connecting-IP;

    # Cloudflare IPv4 (пример)
    set_real_ip_from 173.245.48.0/20;
    set_real_ip_from 103.21.244.0/22;
    set_real_ip_from 103.22.200.0/22;

    # Cloudflare IPv6 (пример)
    set_real_ip_from 2400:cb00::/32;
    set_real_ip_from 2606:4700::/32;

    real_ip_recursive off;
}

Важно: подсети Cloudflare нужно держать актуальными. Если пропустить часть диапазонов, часть трафика будет приходить «как будто» от IP Cloudflare, и вы получите смешанные логи и странное поведение лимитов.

Если вы всё же хотите использовать X-Forwarded-For (например, из-за совместимости с приложением), делайте это аккуратно и с доверенными диапазонами Cloudflare в set_real_ip_from. В таком варианте чаще включают real_ip_recursive on;, особенно если есть промежуточные прокси.

Где размещать директивы: http, server или location

Директивы set_real_ip_from, real_ip_header и real_ip_recursive можно располагать на уровне http или server. Практически:

  • если все ваши сайты за одним и тем же CDN/прокси — удобнее задать в http;
  • если часть доменов идёт напрямую, а часть через CDN — разделяйте по server, чтобы не трогать «прямые» проекты.

На уровне location это обычно не нужно: real IP важен для логов и лимитов в целом, а не для одного URL.

Как проверить, что real IP реально работает

1) Посмотреть переменные в access_log

Сделайте временный лог-формат, чтобы увидеть «до/после»:

http {
    log_format realip_debug 'remote_addr=$remote_addr realip_remote_addr=$realip_remote_addr xff="$http_x_forwarded_for" cf="$http_cf_connecting_ip"';

    access_log /var/log/nginx/realip_debug.log realip_debug;
}

Подсказка по переменным:

  • $remote_addr — адрес, который Nginx использует как IP клиента (после realip-модуля он изменится);
  • $realip_remote_addr — «оригинальный» адрес источника до подмены (часто это IP прокси/CDN).

2) Сравнить поведение rate limit

Если у вас включён limit_req, то до настройки real IP лимиты «склеивают» всех посетителей в один IP (узел CDN). После корректной настройки каждый клиент будет ограничиваться отдельно.

3) Тест на подмену заголовка (X-Forwarded-For spoofing)

Идея: отправить запрос напрямую на origin (в обход CDN) и подставить фейковый X-Forwarded-For. При корректной настройке Nginx не должен принимать этот IP за реальный, если источник запроса не из set_real_ip_from.

Пример проверки (подставьте ваш origin IP или прямой хост):

curl -I -H 'X-Forwarded-For: 1.2.3.4' -H 'X-Real-IP: 1.2.3.4' http://YOUR_ORIGIN_IP/

Дальше смотрите realip_debug.log: если $remote_addr стал 1.2.3.4, значит вы где-то переоткрыли доверие слишком широко.

Проверка real IP через curl и анализ переменных в debug access_log Nginx

Типовые ошибки и как их быстро диагностировать

Ошибка 1: доверяют всем

Симптомы: в логах появляются «красивые» IP, но лимиты/бан-листы ведут себя странно; расследования упираются в поддельные адреса.

Проверьте, нет ли у вас:

  • set_real_ip_from 0.0.0.0/0;
  • set_real_ip_from ::/0;
  • случайно добавленной слишком широкой подсети, не относящейся к вашему прокси/CDN.

Ошибка 2: выбран не тот заголовок

Симптомы: $remote_addr не меняется, хотя вы уверены, что прокси передаёт IP.

Что делать: в debug-лог добавьте нужные $http_* переменные (как в примере выше) и убедитесь, что заголовок действительно доходит до вашего Nginx и не «обрезается» промежуточным прокси.

Ошибка 3: забыли IPv6

Симптомы: часть запросов показывает real IP корректно, часть — нет; особенно заметно там, где активно используется IPv6.

Решение: добавить IPv6-подсети вашего CDN/прокси в set_real_ip_from.

Ошибка 4: несколько прокси в цепочке, а real_ip_recursive не настроен

Симптомы: $remote_addr становится IP «не того» прокси или берётся крайний адрес из X-Forwarded-For.

Если у вас цепочка вида CDN → внутренний LB → Nginx, то обычно нужно:

  • внести в set_real_ip_from и CDN, и внутренний LB;
  • включить real_ip_recursive on; при использовании X-Forwarded-For.

Практика: как вести логи, чтобы не потерять реальность

Даже при корректной настройке полезно логировать «обе стороны»: и итоговый клиентский адрес, и IP прокси, который к вам пришёл. Это помогает при разборе аномалий и споров «кто на самом деле стучался».

Пример расширенного формата (для постоянного использования, если место в логах позволяет):

http {
    log_format main_ext '$remote_addr realip_src=$realip_remote_addr - $remote_user [$time_local] '
                        '"$request" $status $body_bytes_sent '
                        '"$http_referer" "$http_user_agent" '
                        'xff="$http_x_forwarded_for"';
}

Если вы храните логи централизованно, наличие realip_src упрощает фильтрацию «какой узел CDN вёл себя странно» или «с какого LB шёл трафик».

Чек-лист безопасной настройки real IP

  1. Определите точный список доверенных прокси (CDN, балансировщики, reverse proxy).
  2. Выберите real_ip_header (для Cloudflare часто удобнее CF-Connecting-IP).
  3. Заполните set_real_ip_from всеми актуальными IPv4/IPv6 подсетями доверенных прокси.
  4. Если используете X-Forwarded-For и прокси несколько — включите real_ip_recursive on;.
  5. Добавьте временный debug-лог и убедитесь, что $remote_addr и $realip_remote_addr ведут себя ожидаемо.
  6. Проведите тест на x-forwarded-for spoofing: прямой запрос на origin с подставным заголовком не должен менять $remote_addr.
FastFox SSL
Надежные SSL-сертификаты
Мы предлагаем широкий спектр SSL-сертификатов от GlobalSign по самым низким ценам. Поможем с покупкой и установкой SSL бесплатно!

Когда лучше не чинить заголовки, а чинить сеть

Самая надёжная схема — когда ваш origin вообще не доступен напрямую из интернета, а принимает трафик только от CDN/балансировщика. Тогда даже если кто-то подделывает заголовки, он не сможет «обойти» доверенную точку входа.

Практически это делается сетевыми ACL/фаерволом на уровне хоста или облака: разрешить входящие на 80/443 только с IP-подсетей CDN и ваших административных адресов. Настройка real IP в Nginx тогда становится не только удобством для логов, но и частью общей модели безопасности.

Если параллельно вы решаете вопросы доменов и подключения CDN на apex-домене, пригодится разбор ALIAS/ANAME для корня домена при работе с CDN.

И отдельно: когда вы корректно восстанавливаете IP клиента и включаете HTTPS, не забывайте про валидный сертификат. Для боевых проектов логичнее использовать проверенные SSL-сертификаты, чтобы браузеры и API-клиенты не резали доверие к вашему трафику.

Поделиться статьей

Вам будет интересно

Debian/Ubuntu: конфликт systemd-resolved DNSStubListener на 127.0.0.53 с dnsmasq, Unbound и BIND OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: конфликт systemd-resolved DNSStubListener на 127.0.0.53 с dnsmasq, Unbound и BIND

Если локальный DNS в Debian или Ubuntu не стартует с ошибкой address already in use, причина часто в systemd-resolved и DNSStubLis ...
Debian/Ubuntu: как исправить NFS mount.nfs: access denied by server while mounting OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: как исправить NFS mount.nfs: access denied by server while mounting

Ошибка mount.nfs: access denied by server while mounting в Debian и Ubuntu обычно указывает на проблему на стороне NFS-сервера: не ...
Debian/Ubuntu: как устранить конфликт systemd-resolved DNSStubListener с BIND9, dnsmasq и AdGuard Home OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: как устранить конфликт systemd-resolved DNSStubListener с BIND9, dnsmasq и AdGuard Home

Если в Debian или Ubuntu DNS-сервер не стартует из-за ошибки port 53 busy, часто причина в systemd-resolved с локальным слушателем ...