У нас есть сайт wpcraft.ru — WordPress с WooCommerce, живёт на одном хостинге в России (Beget). Аудитория смешанная: основная масса из РФ, но есть и зарубежные посетители.
Проблема простая: зарубежный посетитель из Европы или США получает каждый запрос с трансатлантическим RTT. TTFB главной страницы из Европы — заметно хуже, чем из Москвы. Статика и HTML идут с одного сервера в РФ для всех.
При этом переносить сайт за границу нельзя — основная аудитория в России, а диапазоны зарубежных CDN у российских операторов периодически деградируют или попадают под фильтрацию. Ставить весь сайт за Cloudflare или другой CDN — значит подставить российскую аудиторию под риск блокировок.
Нужно было решение, которое:
- Ускоряет зарубежный трафик через edge-кэш ближе к посетителю.
- Не трогает российскую аудиторию — она продолжает идти напрямую на хостинг в РФ.
- Не требует второго сервера или переезда данных.
Превью ссылок не работают для заребуженых соцсетей и мессенджеров
Менее очевидная, но критичная для контентного сайта проблема: краулеры мессенджеров и соцсетей физически не могут достучаться до origin в РФ, из-за чего ссылки на сайт публикуются без превью (карточки Open Graph).
- Когда пользователь постит ссылку в Telegram, Facebook, X (Twitter), WhatsApp, Slack, Discord, LinkedIn — платформа отправляет своего бота скачать HTML и извлечь
og:title,og:description,og:image. - Маршрут от этих ДЦ до российского хостинга нестабилен: часть транзитных операторов деградировала после 2022, пакеты теряются, RTT превышает таймаут краулера (обычно 5–10 сек). Хостинг в РФ дополнительно может фильтровать «подозрительные» зарубежные диапазоны на уровне DDoS-защиты.
- Результат: краулер получает
timeoutилиconnection reset, платформа публикует «голую» ссылку без картинки и описания. - Для контент-проекта это прямая потеря CTR и охвата — пост со ссылкой без превью в Telegram-канале выглядит мёртво.
Альтернативы и выбор
Классический подход «поставь CDN перед сайтом» здесь не работает:
- Cloudflare и аналоги — единая точка входа для всего мира. Если их IP попадут под фильтрацию в РФ, сайт ляжет для основной аудитории.
- Второй сервер за границей — дорого, сложно синхронизировать, данные разъезжаются.
- GeoDNS с двумя A-записями — не работает с CDN: у pull zone нет постоянного IP, CDN работает на GeoIP, а не на Anycast.
Мы выбрали Bunny.net: у них есть и DNS-хостинг, и CDN, и Scriptable DNS — возможность писать логику резолва на JavaScript прямо в DNS-зоне.
Составляющие и особенности
Архитектура решения
Краулер Telegram/Meta/X — это обычный анонимный GET-запрос из-за рубежа. После включения гео-сплита он попадает на ближайший PoP Bunny (EU/US), получает закэшированный HTML с OG-тегами за ~50 мс вместо таймаута до РФ. Превью начинает генерироваться стабильно, причём без единого обращения к origin — edge отдаёт кэш даже если origin в этот момент недоступен из-за рубежа.
Дополнительный бонус: тот же механизм чинит превью для поисковых ботов и AI-краулеров (Googlebot из зарубежных пулов, meta-webindexer, GPTBot, ClaudeBot, PerplexityBot) — они тоже получают быстрый ответ с edge, что положительно сказывается на краул-бюджете и индексации.
Запрос из РФ Запрос из-за рубежа
│ │
▼ ▼
┌─────────────┐ ┌─────────────────┐
│ Bunny DNS │ │ Bunny DNS │
│ (Script) │ │ (Script) │
│ RU → A │ │ other → PullZone│
└──────┬──────┘ └────────┬────────┘
│ │
▼ ▼
┌─────────────┐ ┌─────────────────┐
│ Origin │ │ Bunny CDN │
│ (Beget, │ │ (edge cache) │
│ 45.130. │ │ │ │
│ 41.97) │ │ ▼ │
└─────────────┘ │ Origin (miss) │
└─────────────────┘
Ключевые компоненты
1. Bunny DNS — авторитативная зона wpcraft.ru. NS переключены с Cloudflare на coco.bunny.net / kiki.bunny.net.
2. Scriptable DNS — скрипт на апексе зоны, который решает, куда отправить запрос:
export default function handleQuery(query) {
const { queryType, clientIP, ednsIP } = query.request;
if (queryType !== 'A') return null;
const ip = ednsIP || clientIP;
let country = null;
try {
country = GeoDatabase.resolve(ip)?.country;
} catch (e) { /* fallback на origin */ }
// РФ и неопознанные — напрямую на хостинг
if (!country || country === 'RU') {
return new ARecord('45.130.41.97', 300);
}
// Остальной мир — через CDN
return new PullZoneRecord('wpcraftru');
}
3. Bunny CDN Pull Zone — origin https://origin.wpcraft.ru, custom hostname wpcraft.ru. Кэширует HTML и статику на edge.
4. Отдельный origin-хостнейм — origin.wpcraft.ru всегда резолвится в хостинг напрямую, не участвует в гео-сплите. Это критично: если бы pull zone смотрела на wpcraft.ru, edge-нода при промахе кэша резолвила бы домен через публичный DNS, получила бы «мировую» ветку (саму себя) и ушла в бесконечную петлю.
Особенности, которые нас укусили
Дефолты Bunny — для статики, не для WordPress. Свежая pull zone создаётся с DisableCookies=true и IgnoreQueryStrings=true. Первое режет Set-Cookie — ломает логин и гостевую корзину полностью. Второе схлопывает ?page=2 и UTM в один ключ кэша — посетитель получает чужую страницу. Каждый из этих параметров по отдельности достаточен, чтобы уронить сайт.
Super Cache не отдаёт Cache-Control. WP Super Cache в expert-режиме отдаёт статический .html через mod_rewrite — PHP не стартует, и заголовка Cache-Control в ответе нет. Edge не получает инструкции о кэшировании. Чинится правилом mod_headers в .htaccess вне маркеров плагина.
Сертификат — курица и яйцо. Bunny валидирует custom hostname по HTTP-01, для этого домен уже должен резолвиться в pull zone. Но направить домен на Bunny без сертификата — TLS-ошибка у всех. Решение: выпустить Let’s Encrypt через DNS-01 (валидация TXT-записью, домен никуда указывать не нужно) и загрузить в pull zone до включения сплита.
TTL делегирования в .RU — 4 суток. Смена NS у регистратора — единственная операция без быстрого отката. TTL делегирования в реестре .RU равен 345600 секунд и не понижается. Откат стоит столько же.
Пошаговое решение
Шаг 0. Инвентаризация
Выгрузили все записи зоны из Cloudflare (12 штук: 7×A, MX, SPF, DKIM), определили реальный origin (в нашем случае 45.130.41.97, зафиксировали бейзлайн TTFB.
Шаг 1. Перенос DNS-зоны
Импортировали зону в Bunny DNS, сверили один-в-один. Владелец сменил NS в REG.RU. Проверили резолв через 8.8.8.8, 77.88.8.8, 1.1.1.1 — всё совпадает.
Что пошло не так: при импорте TTL у 11 из 12 записей стал 1 секунда — резолверы перезапрашивали зону на каждый запрос. Исправили на 5 минут или 300 секунд. Также завели явную A-запись origin.wpcraft.ru — для обхода петли обновления Edge Cache CDN.
Шаг 2. TLS для origin
Привязали origin.wpcraft.ru к тому же сайту в панели Beget, выпустили Let’s Encrypt. Сертификат покрывает оба имени. Побочный бонус: при обращении с Host: origin.wpcraft.ru WordPress отдаёт 302 на канонический домен — служебное имя закрыто от индексации.
Шаг 3. Переконфигурация pull zone
Через Bunny API обновили 8 критических параметров:
| Параметр | Было | Стало | Зачем |
|---|---|---|---|
OriginUrl | https://wpcraft.ru | https://origin.wpcraft.ru | убрать петлю |
OriginHostHeader | пусто | wpcraft.ru | WordPress видит канонический домен |
DisableCookies | true | false | не ломать логин и корзину |
IgnoreQueryStrings | true | false | не схлопывать пагинацию и UTM |
EnableTLS1 / EnableTLS1_1 | true | false | убрать легаси-протоколы |
VerifyOriginSSL | false | true | валидировать сертификат origin |
CacheControlMaxAgeOverride | -1 | 3600 | временный TTL 1 час до выката purge-хука |
Шаг 4. Edge Rules (bypass приватных путей)
Шесть правил Bypass Cache для путей, которые никогда не должны кэшироваться:
/wp-admin*и/wp-login.php*— админка и вход (критично: Beget отдаёт их как JS-заглушку антибот-защиты, которая кэшируется на 30 дней и создаёт вечный цикл перезагрузки)/wp-json*— REST API/my*,/cart*,/checkout*— личный кабинет, корзина, оформление заказа
Важно: bypass делается по путям, не по кукам. Это осознанное решение — наш CacheConfig.php намеренно отдаёт залогиненному пользователю тот же публичный HTML, что и гостю, на кэшируемых маршрутах. Bypass по куке wordpress_logged_in_* сломал бы эту схему.
Шаг 5. Правки на origin
CacheConfig.php:max-ageснижен с 33 до 7 дней — единая точка правды по TTL..htaccess: блокmod_headersдля отдачиCache-Controlна статических попаданиях Super Cache.
Шаг 6. Сертификат для wpcraft.ru на Bunny
Выпуск через DNS-01, загрузка в pull zone. Без этого мировая ветка — TLS-ошибка у всех.
Шаг 7. Гео-сплит
Script record на апексе: RU → ARecord, остальные → PullZoneRecord. Обкатка на geotest.wpcraft.ru, затем апекс и www. Проверка резолва из 5+ регионов через dig +subnet.
Нюансы
- Дефолты CDN убьют ваш WordPress.
DisableCookies=trueиIgnoreQueryStrings=true— дефолты Bunny «для статики». На динамическом сайте каждый из них по отдельности ломает логин, корзину или пагинацию. Проверяйте все параметры pull zone перед включением. - Петля origin — реальный блокер. Если origin pull zone совпадает с публичным доменом, edge-нода при промахе кэша резолвит домен через публичный DNS и получает саму себя. Всегда используйте отдельный origin-хостнейм, который не участвует в гео-сплите.
- Scriptable DNS решает то, что не решают record sets. Geolocation routing в Bunny работает только для A/AAAA. Pull Zone-запись в гео-набор не встаёт. Script record с
PullZoneRecord— единственный рабочий механизм, и он же даёт сплит по коду страны вместо «близости к Москве». - Сертификат — до сплита, не после. HTTP-01 валидация требует, чтобы домен уже резолвился в CDN. DNS-01 не требует. Выпускайте сертификат через DNS-01 и загружайте до включения гео-сплита — иначе получите окно с TLS-ошибкой у всех.
- TTL делегирования в .RU — 4 суток. Смена NS — единственная операция без быстрого отката. Планируйте окно наблюдения 96+ часов и не включайте гео-сплит до его истечения.
- Bypass по путям, не по кукам. Если ваша логика кэширования намеренно отдаёт залогиненным публичный HTML — bypass по кукам сломает эту схему. Отделяйте персональные страницы путями (
/my,/cart,/wp-admin), а не состоянием сессии. - Проверяйте, что
Cache-Controlреально доезжает. WP Super Cache в expert-режиме отдаёт статику без PHP — и безCache-Control. Edge не получит инструкции о кэшировании. Правилоmod_headersв.htaccessрешает это, но требует проверки на боевом сервере.
Выводы
- Зарубежная аудитория получает HTML и статику с ближайшего PoP Bunny — TTFB из Европы падает с трансатлантического до локального (200-400 мс превращаются в 50-100 мс).
- Российская аудитория идёт напрямую на хостинг в РФ — Bunny полностью убран из критического пути.
- Origin один, данные не разъезжаются, локализация ПДн не нарушается.
- Нагрузка на PHP снижается — зарубежный трафик обслуживается edge-кэшем.