Почему не работает OG-превью из-за РФ-хостинга и как исправить: GeoDNS + Bunny CDN с кэшем HTML (что, как и сколько)

У нас есть сайт wpcraft.ru — WordPress с WooCommerce, живёт на одном хостинге в России (Beget). Аудитория смешанная: основная масса из РФ, но есть и зарубежные посетители.

Проблема простая: зарубежный посетитель из Европы или США получает каждый запрос с трансатлантическим RTT. TTFB главной страницы из Европы — заметно хуже, чем из Москвы. Статика и HTML идут с одного сервера в РФ для всех.

При этом переносить сайт за границу нельзя — основная аудитория в России, а диапазоны зарубежных CDN у российских операторов периодически деградируют или попадают под фильтрацию. Ставить весь сайт за Cloudflare или другой CDN — значит подставить российскую аудиторию под риск блокировок.

Нужно было решение, которое:

  1. Ускоряет зарубежный трафик через edge-кэш ближе к посетителю.
  2. Не трогает российскую аудиторию — она продолжает идти напрямую на хостинг в РФ.
  3. Не требует второго сервера или переезда данных.

Превью ссылок не работают для заребуженых соцсетей и мессенджеров

Менее очевидная, но критичная для контентного сайта проблема: краулеры мессенджеров и соцсетей физически не могут достучаться до origin в РФ, из-за чего ссылки на сайт публикуются без превью (карточки Open Graph).

  • Когда пользователь постит ссылку в Telegram, Facebook, X (Twitter), WhatsApp, Slack, Discord, LinkedIn — платформа отправляет своего бота скачать HTML и извлечь og:titleog:descriptionog: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.877.88.8.81.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 критических параметров:

ПараметрБылоСталоЗачем
OriginUrlhttps://wpcraft.ruhttps://origin.wpcraft.ruубрать петлю
OriginHostHeaderпустоwpcraft.ruWordPress видит канонический домен
DisableCookiestruefalseне ломать логин и корзину
IgnoreQueryStringstruefalseне схлопывать пагинацию и UTM
EnableTLS1 / EnableTLS1_1truefalseубрать легаси-протоколы
VerifyOriginSSLfalsetrueвалидировать сертификат origin
CacheControlMaxAgeOverride-13600временный 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.phpmax-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.

Нюансы

  1. Дефолты CDN убьют ваш WordPress. DisableCookies=true и IgnoreQueryStrings=true — дефолты Bunny «для статики». На динамическом сайте каждый из них по отдельности ломает логин, корзину или пагинацию. Проверяйте все параметры pull zone перед включением.
  2. Петля origin — реальный блокер. Если origin pull zone совпадает с публичным доменом, edge-нода при промахе кэша резолвит домен через публичный DNS и получает саму себя. Всегда используйте отдельный origin-хостнейм, который не участвует в гео-сплите.
  3. Scriptable DNS решает то, что не решают record sets. Geolocation routing в Bunny работает только для A/AAAA. Pull Zone-запись в гео-набор не встаёт. Script record с PullZoneRecord — единственный рабочий механизм, и он же даёт сплит по коду страны вместо «близости к Москве».
  4. Сертификат — до сплита, не после. HTTP-01 валидация требует, чтобы домен уже резолвился в CDN. DNS-01 не требует. Выпускайте сертификат через DNS-01 и загружайте до включения гео-сплита — иначе получите окно с TLS-ошибкой у всех.
  5. TTL делегирования в .RU — 4 суток. Смена NS — единственная операция без быстрого отката. Планируйте окно наблюдения 96+ часов и не включайте гео-сплит до его истечения.
  6. Bypass по путям, не по кукам. Если ваша логика кэширования намеренно отдаёт залогиненным публичный HTML — bypass по кукам сломает эту схему. Отделяйте персональные страницы путями (/my/cart/wp-admin), а не состоянием сессии.
  7. Проверяйте, что 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-кэшем.
Фото аватара

Antony I

Веб разработчик, специализация на лучших мировых практиках: WordPress, WooCommerce, NextJS, Strapi, JAMStack ...

Основные типы проектов: CMS, eCommerce, SEO, LMS, ECM, BPM

Ответить

Ваш адрес email не будет опубликован. Обязательные поля помечены *