Зачем вообще «правильно настраивать» Nginx за Cloudflare
Cloudflare в режиме CDN/Reverse Proxy закрывает ваш origin (сервер с Nginx) от прямого трафика и берёт на себя TLS, кеширование, защиту от DDoS и часть WAF-функций. Но у такого «прокси перед Nginx» есть побочный эффект: для Nginx источником запроса становится IP Cloudflare, а не IP посетителя. Если ничего не делать, в логах и в приложении вы увидите не клиента, а адреса Cloudflare, а любые ограничения по IP/странам могут работать не так, как ожидается.
Ниже соберём рабочую, безопасную и обслуживаемую схему:
- корректный real client IP на стороне Nginx;
- ограничение прямого доступа к origin (минимизация WAF bypass);
- IP allowlist для админки/API/чувствительных путей;
- monitoring allowlist для внешних проверок (Blackbox/Uptime и т.п.);
- геоблокировки: на периметре (Cloudflare) и на origin через GeoIP2;
- проверки и типовые ошибки.
Базовая модель трафика и главные риски
Когда сайт за Cloudflare, ваш Nginx чаще всего принимает трафик только от edge-адресов Cloudflare. Нормальный поток выглядит так: клиент → Cloudflare → Nginx → приложение.
Самые частые проблемы в проде:
- Сломанная идентификация клиента: лимиты, антифрод, бан по IP, rate limiting, аудит — всё «видит» Cloudflare.
- Обход защиты (WAF bypass): злоумышленник идёт напрямую на IP origin, минуя Cloudflare/WAF/кеш и иногда даже TLS-настройки.
- Ложные allowlist/denylist: вы разрешили доступ по IP, но это оказался IP Cloudflare, и вы фактически «разрешили всем».
- Мониторинг не работает: вы закрыли origin так, что проверки доступности перестали проходить (или наоборот — оставили «дыру»).
Главная идея: Cloudflare — это периметр. Но origin тоже должен быть защищён так, чтобы прямой доступ был либо невозможен, либо строго контролируем.

Шаг 1. Возвращаем реальный IP клиента в Nginx
Чтобы Nginx знал реальный адрес клиента, нужно доверять заголовку, который Cloudflare добавляет к запросу. Обычно это CF-Connecting-IP. Также Cloudflare выставляет X-Forwarded-For, но безопаснее опираться на CF-заголовок, если запрос пришёл именно от Cloudflare.
Критически важно: доверять этому заголовку можно только если запрос пришёл с IP Cloudflare. Иначе любой сможет прийти напрямую и подставить себе «красивый» IP в заголовке, сломав allowlist/бан/лимиты.
Вариант A (рекомендованный): realip по CF-Connecting-IP + доверие только Cloudflare
1) Сформируйте include-файл с диапазонами Cloudflare для set_real_ip_from. Делать это руками неудобно, поэтому обычно генерируют файл по расписанию (cron/systemd timer) и валидируют конфиг перед reload.
curl -fsS https://www.cloudflare.com/ips-v4 -o /etc/nginx/cloudflare-ips-v4.txt
curl -fsS https://www.cloudflare.com/ips-v6 -o /etc/nginx/cloudflare-ips-v6.txt
awk '{print "set_real_ip_from " $1 ";"}' /etc/nginx/cloudflare-ips-v4.txt > /etc/nginx/cloudflare-realip.conf
awk '{print "set_real_ip_from " $1 ";"}' /etc/nginx/cloudflare-ips-v6.txt >> /etc/nginx/cloudflare-realip.conf
nginx -t
2) Подключите include и включите realip в http-контексте:
http {
include /etc/nginx/cloudflare-realip.conf;
real_ip_header CF-Connecting-IP;
real_ip_recursive on;
log_format main '$remote_addr - $host [$time_local] "$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" cf_ray=$http_cf_ray cf_ip=$http_cf_connecting_ip xff="$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
}
Что это даёт: если запрос пришёл от Cloudflare, $remote_addr станет реальным IP клиента (из CF-Connecting-IP), и дальше allow/deny, rate limiting и аудит начнут работать «как до CDN».
Вариант B: доверять X-Forwarded-For (только при необходимости)
Если перед Nginx есть ещё один доверенный прокси (например, внутренний балансировщик), иногда проще строить цепочку на X-Forwarded-For. Смысл тот же: set_real_ip_from должен содержать только доверенные адреса (Cloudflare и ваши внутренние прокси), иначе получите подделку IP.
Шаг 2. Закрываем прямой доступ к origin (анти WAF bypass)
Настройка realip — это половина дела. Вторая половина: сделать так, чтобы на ваш Nginx нельзя было прийти напрямую «с интернета», минуя Cloudflare. Иначе любой желающий сможет обойти country block, WAF, rate limiting и кеш, а также получать ответы origin «как есть».
Правильный уровень: сетевой firewall
Лучше всего ограничивать доступ на уровне firewall (nftables/iptables/security group) и разрешать входящие 80/443 только с IP-диапазонов Cloudflare.
- входящие 80/443: allow Cloudflare IP ranges, deny all;
- SSH и админ-доступ к серверу: отдельные правила (только ваш офис/VPN/bastion);
- если нужен прямой доступ для мониторинга — разрешайте точечно и фиксируйте исключения в документации.
Если сетевой фильтр недоступен, минимум можно сделать на уровне Nginx через allow/deny, но это слабее: трафик всё равно дойдёт до веб-сервера и потратит CPU/коннекты.
Nginx-ограничение как дополнительная страховка
Идея: обслуживаем запросы только если источник — Cloudflare. Для этого используют include со списком диапазонов и применяют allow/deny на уровне server.
server {
listen 443 ssl;
server_name example.com;
include /etc/nginx/cloudflare-allow.conf;
deny all;
# остальная конфигурация
}
Файл /etc/nginx/cloudflare-allow.conf выглядит так:
allow 173.245.48.0/20;
allow 103.21.244.0/22;
allow 2400:cb00::/32;
# ...
Если вы переносите проект на новый сервер или меняете IP, заранее продумайте миграцию DNS/HTTPS и редиректов. В некоторых сценариях полезно свериться с практиками из заметки про переезд домена и включение HSTS: миграция домена с 301, HSTS и SSL без потерь.
Шаг 3. IP allowlist для админки и чувствительных путей
Даже если сайт публичный, админка, внутренние панели и диагностические эндпоинты часто должны быть доступны только из доверенных сетей. Важно не перепутать: если вы делаете allowlist, используя $remote_addr, то сначала нужен корректный realip (шаг 1), иначе вы будете разрешать IP Cloudflare.
Пример: закрыть /admin только для корпоративного VPN и пары адресов
geo $is_admin_allowed {
default 0;
203.0.113.10 1;
198.51.100.0/24 1;
}
server {
listen 443 ssl;
server_name example.com;
location ^~ /admin/ {
if ($is_admin_allowed = 0) { return 403; }
proxy_pass http://app_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Да, if в Nginx часто ругают, но для простого return он безопасен. Альтернатива — использовать allow/deny прямо в location, но тогда сложнее переиспользовать списки и комбинировать правила.
Если админка доступна только по HTTPS
Не забывайте, что Cloudflare может завершать TLS на периметре, но origin всё равно должен быть настроен аккуратно. Для проектов, где важна корректная цепочка сертификатов и строгие политики, может пригодиться отдельный сертификат на origin и понятная схема обновления. Если нужно централизованно закрыть вопрос с сертификатами, посмотрите SSL-сертификаты для публичного домена.

Шаг 4. Monitoring allowlist: как не «ослепить» мониторинг
С мониторингом типичная дилемма: вы закрыли origin только для Cloudflare, а внешние проверки доступности (сторонний Uptime, Blackbox за пределами Cloudflare) перестали ходить напрямую.
Три практичных подхода:
- Мониторить через Cloudflare: проверки ходят на публичный домен как обычный клиент. Это показывает картину «как видит пользователь», но не даёт прямой сигнал о здоровье origin.
- Сделать отдельный origin health endpoint на отдельном vhost и разрешить доступ только с IP мониторинга (monitoring allowlist). Так вы контролируете и периметр, и origin.
- Разрешить мониторинг на 80/443 как исключение в firewall (только конкретные IP). Проще, но увеличивает поверхность атаки, если список IP изменится или утечёт.
Пример отдельного vhost для healthcheck с allowlist
Выделяем под мониторинг отдельный поддомен, включаем строгий allowlist и отдаём минимальный статичный ответ.
server {
listen 443 ssl;
server_name origin-health.example.com;
allow 203.0.113.50;
allow 203.0.113.51;
deny all;
location = /healthz {
add_header Content-Type text/plain;
return 200 "ok";
}
}
Так вы отделяете «боевой» трафик от мониторингового и не вынуждены расширять доступ к основному домену.
Шаг 5. Геоблокировки: Cloudflare country block vs Nginx GeoIP2
Здесь два пути: блокировать на периметре (Cloudflare) или на origin (Nginx через GeoIP2). На практике лучше начинать с периметра: трафик даже не доходит до сервера, вы экономите ресурсы и снижаете риск атак.
Cloudflare country block: быстро и эффективно
Блокировки по стране на уровне Cloudflare хороши тем, что запросы отсекаются до origin. Минус: в логах origin вы не увидите «попытки» из заблокированных стран, а правила живут в конфигурации Cloudflare и требуют дисциплины в изменениях.
Nginx GeoIP2: гибко, но требует сопровождения
GeoIP2 позволяет определять страну по IP. Важно: работать это будет корректно только если уже настроен real client IP, иначе Nginx будет геолоцировать Cloudflare, а не пользователя.
http {
geoip2 /usr/share/GeoIP/GeoLite2-Country.mmdb {
$geoip2_country_code source=$remote_addr country iso_code;
}
map $geoip2_country_code $is_country_blocked {
default 0;
RU 0;
KZ 0;
BY 0;
CN 1;
IR 1;
}
server {
listen 443 ssl;
server_name example.com;
if ($is_country_blocked = 1) { return 403; }
# ...
}
}
Плюсы: можно не только «банить», но и маршрутизировать по стране, включать разные лимиты, писать отдельные логи/метрики.
Минусы: базу GeoIP нужно обновлять, а решения о блокировке будут распределены между Cloudflare и origin, поэтому это стоит документировать.
Шаг 6. Типовые ошибки, из-за которых ломается безопасность
1) Доверие заголовку без ограничения источника
Если вы включили real_ip_header, но не ограничили set_real_ip_from списком Cloudflare, любой прямой запрос сможет подделать IP и «пройти» ваши allowlist/баны/лимиты.
2) Открытый origin (WAF bypass)
Если origin доступен всем, то WAF, country block, rate limiting, bot-фильтры и кеш Cloudflare легко обходятся прямым запросом на IP origin. В логах это часто выглядит как «странные» запросы, которых не видно на периметре.
3) Мониторинг как «дырка»
Частая ошибка — разрешить слишком широкие подсети «провайдера мониторинга». Лучше держать monitoring allowlist узким и отдельным, а health endpoint — минимальным.
4) GeoIP на периметре и на origin без единой логики
Если часть стран режется в Cloudflare, а часть — в Nginx, через пару месяцев никто не вспомнит «почему». Минимум: комментарии в конфиге и короткий README рядом с инфраструктурным репозиторием.
Шаг 7. Проверки: как убедиться, что всё работает
Проверяем real client IP в логах
Временно добавьте в формат лога значения $http_cf_connecting_ip, $remote_addr и $http_x_forwarded_for (пример выше). После тестов можно оставить CF-поля: CF-RAY часто помогает быстро сопоставлять запросы на стороне Cloudflare и на стороне origin.
Проверяем, что origin не доступен напрямую
Попробуйте обратиться к origin по его IP (или по отдельному DNS, который указывает напрямую), минуя Cloudflare. Должно быть 403/444 или отсутствие соединения — в зависимости от того, где вы закрыли доступ (firewall или Nginx).
Проверяем allowlist для админки
С доверенного IP админка открывается. С недоверенного — стабильный 403. Обязательно проверяйте «как в реальности»: через Cloudflare, чтобы убедиться, что realip уже применился до проверки.
Набор практических рекомендаций на будущее
- Храните списки Cloudflare IP как генерируемые файлы и обновляйте по расписанию (с
nginx -tперед reload). - Правило по умолчанию: origin закрыт. Разрешения — только Cloudflare и минимальные исключения (мониторинг/админские сети).
- Старайтесь не плодить allowlist в разных местах: используйте
geo/mapи include-файлы. - Для расследований полезно логировать
CF-RAYиCF-Connecting-IPрядом с$request_id(если используете). - Геоблокировки по возможности делайте на периметре, а GeoIP2 оставляйте для тонкой логики и независимых проверок.
Сначала обеспечьте достоверный real client IP, затем закройте origin от обхода, и только потом стройте allowlist, country block и monitoring allowlist — иначе правила будут либо не работать, либо работать «наоборот».
Чек-лист конфигурации (коротко)
- Есть актуальный список Cloudflare IP (v4+v6) и он подключён в Nginx через
set_real_ip_from. - Включены
real_ip_header CF-Connecting-IPиreal_ip_recursive on. - Origin закрыт на firewall или хотя бы на уровне Nginx (только Cloudflare + необходимые исключения).
- Админка/чувствительные пути защищены отдельным IP allowlist и он использует корректный
$remote_addr. - Мониторинг не ломает безопасность: отдельный endpoint или точечные исключения (monitoring allowlist).
- Геоблокировки понятны и документированы: на Cloudflare и/или через GeoIP2 на origin.


