Два кэша воюют: CDN против Bitrix Composite
Мы подключили Cloudflare к Bitrix-магазину в четверг вечером. В пятницу утром пошли жалобы: «корзина пустая, хотя добавлял». К субботе — «личный кабинет показывает чужие данные».
Данные были не чужие. Это просто CDN отдавал кэшированную страницу одного пользователя другому. Но звучит так, будто у вас дыра в безопасности.
Composite mode и CDN делают правильные вещи. Просто по отдельности.
Как работает Bitrix Composite
Composite mode — это фича Bitrix, которая превращает динамическую PHP-страницу в статику при первом запросе. На диск сохраняется HTML-снимок. Следующий пользователь получает этот файл напрямую, без выполнения PHP.
Смысл простой: вместо 200–400 мс на генерацию страницы — 5–15 мс на отдачу статического файла. На нагрузке это ощутимо.
Composite умеет «дырки» — компоненты, которые остаются динамическими: корзина, авторизация, личные цены. PHP рендерит их отдельно, потом JS вставляет на место. Итог: скелет страницы — статика, персональные части — динамика.
Проблема начинается, когда между пользователем и PHP появляется ещё один слой кэша.
Что CDN кэширует из composite-ответа
CDN смотрит на HTTP-ответ и решает: кэшировать или нет. По умолчанию он ориентируется на заголовки Cache-Control и Expires. Если Bitrix отдаёт страницу без Cache-Control: private или no-store — CDN её кэширует.
Теперь представьте: пользователь А заходит на страницу товара. Composite генерирует HTML-снимок, Bitrix отдаёт его с заголовком, который CDN воспринимает как кэшируемый. CDN сохраняет ответ на своём edge-сервере. Пользователь Б делает тот же запрос с того же геолокационного узла — получает закэшированный ответ пользователя А.
Если на странице была корзина пользователя А в виде динамического компонента — CDN её не кэшировал. Но если Bitrix не успел поставить правильный Cache-Control: no-store на ответ с куками авторизации — CDN закэшировал сессионный контекст. Отсюда ощущение «чужих данных».
На нашем проекте это случилось за 18 часов: 340 обращений в поддержку за выходные.
Три сценария конфликта
Корзина выглядит пустой. Composite генерирует скелет страницы без корзины — она динамическая. CDN кэширует скелет. При следующем посещении JS пытается загрузить корзину, но CDN отдаёт старый ответ на AJAX-запрос к /bitrix/components/bitrix/sale.basket.basket/. Корзина не грузится или грузится с задержкой, которая выглядит как «пустая».
Авторизованный пользователь видит неавторизованную страницу. CDN закэшировал версию без сессионного контекста (или с контекстом другого пользователя). Пользователь видит «Войти», хотя только что залогинился. Выглядит как выброс из сессии.
Персональные цены и акции не применяются. Composite корректно помечает блок цен как динамический. Но CDN перехватывает AJAX-запрос к ajax.php и отдаёт закэшированный ответ с ценами без скидки другого пользователя. На высоком трафике это заметно.
Как диагностировать конфликт
Первый шаг — проверить, откуда пришёл ответ. В Cloudflare это заголовок CF-Cache-Status: значение HIT означает, что ответ пришёл из CDN-кэша. Если HIT есть на страницах с персональным контентом — проблема там.
Второй шаг — посмотреть Set-Cookie в ответах. Если Bitrix ставит куки авторизации (PHPSESSID, BITRIX_SM_LOGIN) в ответе, который CDN закэшировал, — это потенциальная утечка контекста между пользователями.
curl -sI "https://example.com/catalog/tovar/" | grep -E "Cache-Control|CF-Cache-Status|Set-Cookie"
Если CF-Cache-Status: HIT и в ответе есть Set-Cookie — это и есть источник проблемы.
Как примирить CDN и Composite
Решение работает на трёх уровнях.
Первое — Cache-Control на PHP-страницах с авторизацией. Bitrix должен ставить Cache-Control: private, no-store на все ответы, где есть сессионный контекст. В .settings.php или в init.php можно добавить обработчик:
// init.php
AddEventHandler("main", "OnEndBufferContent", function() {
if (CUser::IsAuthorized()) {
header("Cache-Control: private, no-store, no-cache");
}
});
Composite к этому моменту уже сработал, так что это не идеальное решение. Но оно убирает риск, что CDN закэширует авторизованный контекст.
Второе — bypass rules по cookie в CDN. В Cloudflare это Cache Rules с условием: если запрос содержит куки PHPSESSID или BITRIX_SM_LOGIN — не кэшировать. CDN-покрытие для авторизованных пользователей снижается, зато конфликт уходит.
Третье — Vary по Cookie для AJAX. Запросы к ajax.php с параметрами сессии должны иметь Vary: Cookie в ответе, чтобы CDN кэшировал их per-cookie, а не per-URL. Или проще — полный bypass AJAX-маршрутов.
После применения всех трёх мер — ноль жалоб на корзину за следующие две недели.
Чеклист перед подключением CDN к Bitrix с Composite
Три вещи, которые нужно проверить до того, как CDN уйдёт в production:
- Проверь
Cache-Controlна авторизованных страницах.curl -sIс куками авторизации — должно бытьprivateилиno-store. - **Настрой bypass по
PHPSESSIDиBITRIX_SM_*в CDN.** Это обязательно, не опционально. - Замерь
CF-Cache-Status: HITтолько на действительно статических страницах (главная без персонализации, статьи, лендинги). Каталог — проверяй отдельно.
Composite mode — хорошая оптимизация для PHP-монолита. CDN — тоже хорошая оптимизация. Но вместе они требуют явного разграничения: что кэшируется на edge, а что должно доходить до PHP всегда.
Это не баг Bitrix и не баг CDN. Это архитектурное решение, которое нужно принять осознанно.
Если интересно, как CDN влияет на производительность Bitrix в целом, читайте про настройку PHP-FPM для Bitrix и про cache warming через Cron — там же разбираем, что происходит, когда кэш холодный.