nginx за Cloudflare: SSL, реальный IP и закрытый origin
Оранжевое облако включается одной кнопкой. Чтобы за ним не остались редирект-петля, IP Cloudflare в логах и origin, открытый всему интернету, нужно ещё несколько файлов.
Cloudflare-прокси часто включают, чтобы «спрятать сервер». Без настройки origin выходит так: сервер по-прежнему доступен напрямую, в логах сплошь адреса Cloudflare, а после включения HTTPS сайт уходит в бесконечный редирект.
Грабли
Сначала о России. По опыту сообщества, IP Cloudflare у части российских операторов недоступны или работают плохо: панель или кабинет за оранжевым облаком из РФ не открывается, а с серым облаком (DNS only) открывается. Примеры: индекс ошибок, DNS. Если твоя аудитория в РФ, сначала проверь домен из нескольких стран через host.pink/check, а сам разбор, почему сайт не открывается, есть в гайде Сайт не открывается из РФ.
Режимы SSL/TLS
В дашборде зоны: SSL/TLS → Overview. Режим определяет, как Cloudflare ходит к твоему серверу, а не к посетителю.
| Режим | Посетитель → Cloudflare | Cloudflare → origin | Проверка сертификата origin |
|---|---|---|---|
| Flexible | HTTPS | всегда HTTP | нет |
| Full | HTTPS | тем же протоколом, что и посетитель | никакой: подойдёт самоподписанный, просроченный, на чужое имя |
| Full (strict) | HTTPS | тем же протоколом, что и посетитель | срок действия, издатель (публичный CA или Cloudflare Origin CA), совпадение имени в CN/SAN |
Почему Flexible — зло
- Редирект-петля. Cloudflare приходит на origin по HTTP. Если на origin стоит
return 301 https://…, nginx отправляет Cloudflare на HTTPS, Cloudflare снова идёт по HTTP, и так без конца. Документация Cloudflare прямо называет это причиной недоступности сайта. - Открытый HTTP до origin. Замочек в браузере есть, но участок Cloudflare → сервер идёт открытым текстом. Сама Cloudflare не рекомендует Flexible для логинов и персональных данных. Панель с паролем администратора как раз такой случай.
Full лучше, но тоже не защищает: раз сертификат не проверяется, «подсунуть свой сертификат» на этом участке технически возможно. Рабочий вариант один: Full (strict). Редирект с HTTP на HTTPS Cloudflare советует делать на своей стороне (принудительный HTTPS на edge), а не на origin.
Сертификат на origin
Для Full (strict) подойдёт любой публично доверенный сертификат (Let's Encrypt и т. п., см. Сертификаты и гайд TLS-сертификаты) или Cloudflare Origin CA: SSL/TLS → Origin Server → Create Certificate.
Что важно знать про Origin CA:
- Ему доверяет только Cloudflare. Если снять проксирование (серое облако) или поставить Cloudflare на паузу, браузеры увидят недоверенный сертификат.
- Ключ RSA или ECC, для nginx выбирай формат PEM.
- До 200 SAN, wildcard только на один уровень (
*.example.com), IP-адреса в SAN нельзя. - Cloudflare не присылает уведомлений об истечении Origin CA. Срок отслеживай сам.
Не делай так
Учитывая предупреждение про РФ: если есть шанс, что придётся быстро переключить домен на серое облако, Origin CA тебя подведёт. Сертификат Let's Encrypt работает в обоих режимах.
Реальный IP посетителя
За прокси $remote_addr в nginx равен адресу узла Cloudflare. Настоящий адрес Cloudflare передаёт в заголовке CF-Connecting-IP, а восстанавливает его модуль ngx_http_realip_module. В сборке из исходников он по умолчанию не включён, поэтому сначала проверь:
nginx -V 2>&1 | grep -o with-http_realip_moduleКонфиг: доверяем заголовку только от адресов Cloudflare.
# https://www.cloudflare.com/ips-v4
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;
# https://www.cloudflare.com/ips-v6
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;
real_ip_header CF-Connecting-IP;Обе директивы допустимы в контекстах http, server и location; убедись, что conf.d/*.conf подключается в nginx.conf внутри блока http. Исходный адрес узла Cloudflare остаётся доступен в переменной $realip_remote_addr, её удобно писать в лог рядом с $remote_addr.
Важно
Список выше снят 2026-09-23. Cloudflare меняет диапазоны редко, но меняет, и сама пишет, что список нужно регулярно обновлять. Скрипт автообновления ниже.
Закрыть origin для всех, кроме Cloudflare
Смысл прокси теряется, если сервер отвечает напрямую: кто узнал IP origin, обходит Cloudflare и все её защиты. Документация Cloudflare советует пропускать на веб-порты только её адреса.
Берём конфиг из гайда
nftables: минимальный файрвол и добавляем в таблицу inet filter два набора, а правило для 80/443 меняем:
table inet filter {
set cf4 { type ipv4_addr; flags interval; }
set cf6 { type ipv6_addr; flags interval; }
chain input {
type filter hook input priority filter; policy drop;
# ... established, lo, icmp, ssh как в базовом конфиге ...
# было: tcp dport { 80, 443 } accept
tcp dport { 80, 443 } ip saddr @cf4 accept
tcp dport { 80, 443 } ip6 saddr @cf6 accept
}
}
# элементы наборов генерирует скрипт, файл подключается после объявления таблицы
include "/etc/nftables.d/cloudflare.nft"Скрипт, который скачивает диапазоны и обновляет и nftables, и nginx:
#!/bin/sh
set -eu
v4=$(curl -fsS https://www.cloudflare.com/ips-v4)
v6=$(curl -fsS https://www.cloudflare.com/ips-v6)
# пустой ответ не должен превратиться в пустой allowlist
[ -n "$v4" ] && [ -n "$v6" ] || exit 1
mkdir -p /etc/nftables.d
{
echo "add element inet filter cf4 { $(echo $v4 | tr ' ' ',') }"
echo "add element inet filter cf6 { $(echo $v6 | tr ' ' ',') }"
} > /etc/nftables.d/cloudflare.nft
# очистка и заполнение в одной транзакции nft -f
printf '%s\n' 'flush set inet filter cf4' 'flush set inet filter cf6' \
'include "/etc/nftables.d/cloudflare.nft"' > /run/cf-apply.nft
nft -f /run/cf-apply.nft
{
for n in $v4 $v6; do echo "set_real_ip_from $n;"; done
echo "real_ip_header CF-Connecting-IP;"
} > /etc/nginx/conf.d/cloudflare-realip.conf
nginx -t && systemctl reload nginxchmod +x /usr/local/sbin/cf-ips-update && /usr/local/sbin/cf-ips-update
nft list set inet filter cf4Файл, загруженный через nft -f, применяется атомарно: момента, когда набор уже очищен, а новые адреса ещё не добавлены, нет. Запускать скрипт раз в сутки удобно через systemd-таймер, как это сделать, описано в гайде
systemd: свои сервисы и таймеры.
Грабли
Если nginx живёт в Docker и порты опубликованы через ports:, Docker пропускает трафик в обход цепочки input, и этот allowlist не сработает. Подробности в
гайде по nftables и в базе:
Docker,
Firewall и анти-DDoS.
Authenticated Origin Pulls (mTLS)
Файрвол проверяет, что запрос пришёл из сети Cloudflare. AOP добавляет криптографическую проверку: Cloudflare предъявляет клиентский сертификат, nginx его проверяет. Варианты:
- Global: общий сертификат Cloudflare. Сама документация предупреждает, что он одинаковый для всех аккаунтов и доказывает только то, что запрос пришёл из сети Cloudflare, а не от твоего аккаунта.
- Zone-level: свой сертификат, загруженный в зону. Действует только для твоего аккаунта.
- Per-hostname: свой сертификат на конкретные имена.
Порядок важен: сначала origin, потом переключатель. Для Global скачай сертификат Cloudflare для AOP по ссылке со страницы Global AOP в документации. Для Zone-level сгенерируй свой через OpenSSL и загрузи в SSL/TLS → Origin Server → Authenticated Origin Pulls. Затем в nginx:
ssl_client_certificate /etc/nginx/certs/cloudflare.crt;
ssl_verify_client optional; # на время проверкиПотом включаешь AOP в дашборде (Origin Server → Authenticated Origin Pulls, секция Global или Zone-level → On), проверяешь, что сайт открывается, и ужесточаешь:
ssl_verify_client on;Без сертификата nginx ответит кодом 496, при ошибке проверки 495. Обе директивы работают в контекстах http и server.
WebSocket и порты
WebSocket через Cloudflare работает на всех тарифах, переключатель находится в Network → WebSockets, проверь, что он включён. На стороне nginx нужны заголовки из официального примера:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 443 ssl;
server_name panel.example.com;
ssl_certificate /etc/nginx/certs/origin.pem;
ssl_certificate_key /etc/nginx/certs/origin.key;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1; # нужно для nginx до 1.29.7
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 1h;
}
}По умолчанию nginx закрывает проксируемое соединение, если бэкенд молчит 60 секунд, поэтому proxy_read_timeout поднят. У Cloudflare свой idle-таймаут, а при выкатке обновлений сети она перезапускает серверы и рвёт WebSocket-соединения. Клиент должен уметь переподключаться, keepalive-пинги тоже помогают.
Cloudflare проксирует не любой порт, а только эти:
| Порты | |
|---|---|
| HTTP | 80, 8080, 8880, 2052, 2082, 2086, 2095 |
| HTTPS | 443, 2053, 2083, 2087, 2096, 8443 |
На 2052, 2053, 2082, 2083, 2086, 2087, 2095, 2096, 8880 и 8443 кэширование по умолчанию отключено. Для остальных портов (SSH и прочего) остаётся серое облако или Spectrum. Если открываешь в nftables, например, 8443 для Cloudflare, добавь его в правила с @cf4/@cf6.
Лайфхак
Как устроены реверс-прокси для панелей и ботов в сообществе (nginx, Caddy, haproxy): Reverse proxy и Nginx / Caddy для панели. Проверить, что origin не светит лишних портов: досье IP.
Источники
- Cloudflare: Flexible, Full, Full (strict)
- Cloudflare: Origin CA certificates
- Cloudflare: Restoring original visitor IPs
- Cloudflare: IP-адреса, ips-v4, ips-v6, Cloudflare IP addresses
- Cloudflare: Authenticated Origin Pulls, Global AOP, Zone-level AOP
- Cloudflare: WebSockets, Network ports
- nginx: ngx_http_realip_module, ngx_http_ssl_module, WebSocket proxying
- nftables: man nft, Sets, Atomic rule replacement