Когда DNS-запись проксируется через Cloudflare, nginx видит IP-адрес узла Cloudflare, а не посетителя. Из-за этого журналы, ограничения запросов, аналитика и приложение могут считать разных пользователей одним клиентом.
Правильное решение — включить модуль ngx_http_realip_module, доверять только официальным сетям Cloudflare и брать адрес посетителя из заголовка CF-Connecting-IP. После настройки реальный адрес будет доступен в nginx как $remote_addr, а в PHP — как $_SERVER['REMOTE_ADDR'].
Когда решение не подходит: инструкция рассчитана на схему, в которой Cloudflare напрямую подключается к nginx. Если перед nginx стоит ещё один балансировщик, reverse proxy, контейнерный ingress или Cloudflare Tunnel, доверять нужно адресу непосредственного прокси, а конфигурацию следует адаптировать под всю цепочку.
Что было неверно в старом варианте
X-Forwarded-Forможет содержать цепочку адресов и значения, добавленные предыдущими прокси. Для обычной схемы Cloudflare → nginx надёжнее использоватьCF-Connecting-IP, содержащий один IP-адрес.- Нельзя доверять заголовку от любого отправителя. Иначе посетитель сможет подставить произвольный IP.
- Команда
curl -I https://example.comне обязана показыватьCF-Connecting-IP: Cloudflare отправляет этот заголовок в запросе к origin-серверу, а не в ответе браузеру. - Диапазоны Cloudflare меняются, поэтому конфигурацию нужно периодически сверять с официальным источником.
1. Проверить модуль realip
nginx -V 2>&1 | grep -- '--with-http_realip_module'
Если команда вывела --with-http_realip_module, модуль доступен. Если вывода нет, установите пакет nginx со встроенным модулем или пересоберите nginx с параметром:
--with-http_realip_module
2. Готовая конфигурация nginx
Создайте файл /etc/nginx/conf.d/cloudflare-realip.conf. Обычно каталог conf.d подключён внутри блока http в /etc/nginx/nginx.conf.
# /etc/nginx/conf.d/cloudflare-realip.conf # # Источник диапазонов: # https://www.cloudflare.com/ips-v4 # https://www.cloudflare.com/ips-v6 # # Проверено: 18.06.2026 # 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; set_real_ip_from 103.31.4.0/22; set_real_ip_from 141.101.64.0/18; set_real_ip_from 108.162.192.0/18; set_real_ip_from 190.93.240.0/20; set_real_ip_from 188.114.96.0/20; set_real_ip_from 197.234.240.0/22; set_real_ip_from 198.41.128.0/17; set_real_ip_from 162.158.0.0/15; set_real_ip_from 104.16.0.0/13; set_real_ip_from 104.24.0.0/14; set_real_ip_from 172.64.0.0/13; set_real_ip_from 131.0.72.0/22; # IPv6 set_real_ip_from 2400:cb00::/32; set_real_ip_from 2606:4700::/32; set_real_ip_from 2803:f800::/32; set_real_ip_from 2405:b500::/32; set_real_ip_from 2405:8100::/32; set_real_ip_from 2a06:98c0::/29; set_real_ip_from 2c0f:f248::/32; # Cloudflare передаёт исходный адрес посетителя в одном значении. real_ip_header CF-Connecting-IP;
Директива real_ip_recursive on здесь не требуется: CF-Connecting-IP содержит один адрес, а не цепочку прокси. Она нужна в других схемах, например при разборе корректно сформированного X-Forwarded-For через несколько доверенных прокси.
3. Проверить конфигурацию и перечитать nginx
nginx -t && systemctl reload nginx
Используйте reload, а не безусловный restart: при успешной проверке nginx перечитает конфигурацию без обрыва активных соединений.
4. Проверить реальный IP через журнал nginx
Временно добавьте в контекст http отдельный формат журнала:
log_format realIpDebug 'client=$remote_addr ' 'cloudflare=$realip_remote_addr ' 'header=$http_cf_connecting_ip ' 'ray=$http_cf_ray ' 'request="$request"'; access_log /var/log/nginx/real-ip-debug.log realIpDebug;
Проверьте конфигурацию, перечитайте nginx и откройте сайт через проксируемый домен:
nginx -t && systemctl reload nginx tail -f /var/log/nginx/real-ip-debug.log
Ожидаемый результат:
client=203.0.113.10 cloudflare=172.70.250.10 header=203.0.113.10 ray=... request="GET / HTTP/2.0"
$remote_addr— восстановленный адрес посетителя.$realip_remote_addr— адрес узла Cloudflare, который подключился к nginx.$http_cf_connecting_ip— исходное значение заголовка.$http_cf_ray— идентификатор запроса Cloudflare, полезный для диагностики.
После проверки удалите отладочный журнал, если он больше не нужен.
Проверка в PHP
После обработки заголовка на уровне nginx приложение должно использовать REMOTE_ADDR, а не читать HTTP_CF_CONNECTING_IP напрямую.
<?php
declare(strict_types=1);
header('Content-Type: text/plain; charset=utf-8');
$clientIp = $_SERVER['REMOTE_ADDR'] ?? '';
if (filter_var($clientIp, FILTER_VALIDATE_IP) === false)
{
http_response_code(500);
echo 'IP-адрес недоступен';
exit;
}
echo $clientIp;
Откройте тестовый файл через домен с включённым проксированием Cloudflare, сравните результат со своим внешним IP и затем удалите файл.
Автоматическое обновление диапазонов Cloudflare
Статический список со временем может устареть. Следующий скрипт загружает официальные IPv4- и IPv6-диапазоны, проверяет их через Python, атомарно создаёт конфигурацию, запускает nginx -t и откатывает изменения при ошибке.
#!/usr/bin/env bash
set -Eeuo pipefail
if (( EUID != 0 ))
then
echo 'Запустите скрипт от root' >&2
exit 1
fi
configFile='/etc/nginx/conf.d/cloudflare-realip.conf'
workDir="$(mktemp -d)"
ipv4File="${workDir}/ips-v4"
ipv6File="${workDir}/ips-v6"
newConfig="${workDir}/cloudflare-realip.conf"
backupFile="${workDir}/cloudflare-realip.conf.backup"
hadConfig=0
trap 'rm -rf "$workDir"' EXIT
if ! nginx -V 2>&1 | grep -q -- '--with-http_realip_module'
then
echo 'nginx собран без ngx_http_realip_module' >&2
exit 1
fi
curl \
--fail \
--silent \
--show-error \
--location \
--proto '=https' \
--tlsv1.2 \
'https://www.cloudflare.com/ips-v4' \
--output "$ipv4File"
curl \
--fail \
--silent \
--show-error \
--location \
--proto '=https' \
--tlsv1.2 \
'https://www.cloudflare.com/ips-v6' \
--output "$ipv6File"
python3 - "$ipv4File" "$ipv6File" <<'PY'
import ipaddress
import pathlib
import sys
for fileName in sys.argv[1:]:
path = pathlib.Path(fileName)
ranges = [
line.strip()
for line in path.read_text(encoding='utf-8').splitlines()
if line.strip()
]
if not ranges:
raise SystemExit(f'Пустой список диапазонов: {path}')
for network in ranges:
ipaddress.ip_network(network, strict=True)
PY
{
echo '# Generated automatically from official Cloudflare ranges'
echo '# https://www.cloudflare.com/ips-v4'
echo '# https://www.cloudflare.com/ips-v6'
echo
while IFS= read -r network
do
&& printf 'set_real_ip_from %s;\n' "$network"
done < "$ipv4File"
echo
while IFS= read -r network
do
&& printf 'set_real_ip_from %s;\n' "$network"
done < "$ipv6File"
echo
echo 'real_ip_header CF-Connecting-IP;'
} > "$newConfig"
if
then
cp -a "$configFile" "$backupFile"
hadConfig=1
fi
install -o root -g root -m 0644 "$newConfig" "$configFile"
restoreConfig()
{
if (( hadConfig == 1 ))
then
cp -a "$backupFile" "$configFile"
else
rm -f "$configFile"
fi
}
if ! nginx -t
then
echo 'Новая конфигурация nginx не прошла проверку. Выполняется откат.' >&2
restoreConfig
nginx -t || true
exit 1
fi
if ! systemctl reload nginx
then
echo 'nginx не смог перечитать конфигурацию. Выполняется откат.' >&2
restoreConfig
nginx -t && systemctl reload nginx || true
exit 1
fi
echo 'Диапазоны Cloudflare обновлены, nginx перечитал конфигурацию.'
Сохраните скрипт, например, как /usr/local/sbin/update-cloudflare-realip:
chmod 750 /usr/local/sbin/update-cloudflare-realip /usr/local/sbin/update-cloudflare-realip
Автоматический запуск через systemd timer
Создайте сервис /etc/systemd/system/cloudflare-realip-update.service:
[Unit] Description=Update Cloudflare IP ranges for nginx After=network-online.target Wants=network-online.target [Service] Type=oneshot ExecStart=/usr/local/sbin/update-cloudflare-realip
Создайте таймер /etc/systemd/system/cloudflare-realip-update.timer:
[Unit] Description=Weekly update of Cloudflare IP ranges [Timer] OnCalendar=Sun *-*-* 04:20:00 RandomizedDelaySec=30m Persistent=true [Install] WantedBy=timers.target
Включите таймер:
systemctl daemon-reload systemctl enable --now cloudflare-realip-update.timer systemctl list-timers cloudflare-realip-update.timer
Безопасность
- Никогда не используйте
set_real_ip_from 0.0.0.0/0илиset_real_ip_from ::/0. Это позволит любому клиенту подменить адрес через HTTP-заголовок. - Не читайте
CF-Connecting-IPнапрямую в PHP без проверки источника запроса. Настройте доверенные сети в nginx и используйтеREMOTE_ADDR. - Закройте прямой доступ к origin-серверу на порты 80 и 443, разрешив Cloudflare и необходимые служебные адреса. Иначе злоумышленник сможет обходить WAF, кеш и ограничения Cloudflare.
- Перед ограничением firewall учтите SSH, мониторинг, балансировщики, резервные системы и проверку сертификатов. Ошибка в allowlist может сделать сайт недоступным и вызвать Cloudflare 521/522.
- Для более строгой защиты origin рассмотрите Authenticated Origin Pulls или Cloudflare Tunnel.
Краевые случаи
- Cloudflare Tunnel: nginx обычно получает соединение от локального
cloudflared. Доверять нужно только локальному адресу или Unix-сокету, через который приходит трафик, а не всем сетям Cloudflare. - Дополнительный reverse proxy: в
set_real_ip_fromукажите адрес непосредственного доверенного прокси. Он должен передаватьCF-Connecting-IPбез возможности подмены. - Pseudo IPv4: при режиме перезаписи заголовков IPv6 посетителя может быть заменён псевдо-IPv4. Исходный IPv6 будет доступен в
CF-Connecting-IPv6. - Отключённый proxy: если DNS-запись в Cloudflare имеет серое облако, запрос пойдёт прямо на сервер и заголовка
CF-Connecting-IPне будет. - Удаление visitor IP headers: проверьте Managed Transforms в Cloudflare, если нужный заголовок не приходит на origin.
- Firewall и fail2ban: сетевой firewall видит IP Cloudflare, а не восстановленный адрес посетителя. Блокировку посетителя выполняйте через Cloudflare WAF/API либо на уровне nginx после обработки realip.
Производительность
Модуль realip выполняет простую проверку адреса соединения по доверенным CIDR-диапазонам и не создаёт заметной нагрузки. После восстановления $remote_addr его можно использовать в limit_req, limit_conn, geo, журналах и приложении.
Совместимость
- nginx с модулем
ngx_http_realip_module; - AlmaLinux 8/9, Rocky Linux, RHEL, Ubuntu и другие Linux-системы;
- IPv4 и IPv6;
- обычное проксирование Cloudflare с оранжевым облаком.
Пути к конфигурации и имя systemd-сервиса nginx могут отличаться в нестандартной сборке или контейнере.
Итоговая проверка
- DNS-запись домена проксируется через Cloudflare.
- nginx собран с
ngx_http_realip_module. - В
set_real_ip_fromнаходятся только официальные сети Cloudflare. - Используется
real_ip_header CF-Connecting-IP. - Команда
nginx -tзавершается успешно. - В журнале
$remote_addrсодержит адрес посетителя, а$realip_remote_addr— адрес Cloudflare. - PHP получает адрес из
$_SERVER['REMOTE_ADDR']. - Прямой доступ к origin ограничен или дополнительно защищён.
Альтернативы
- Cloudflare Tunnel: origin не требует публичного входящего подключения.
- Authenticated Origin Pulls: nginx проверяет клиентский сертификат Cloudflare.
- PROXY protocol: применяется в других балансировщиках и L4-схемах, но требует отдельной настройки
listen ... proxy_protocol. - mod_remoteip: аналогичная настройка для Apache.
Комментарии
0Нашли ошибку или хотите дополнить материал?