Back to blog

When CDN and Bitrix Composite fight each other

We added Cloudflare to a Bitrix store on Thursday evening. By Friday morning: "my cart is empty, I already added items." By Saturday: "the account page is showing someone else's data."

It wasn't someone else's data. It was CDN serving a cached response from another user's session. But when a client says "someone else's data," you don't have time to explain cache mechanics.

340 support tickets over a weekend. From one deployment.

Composite mode did the right thing. CDN did the right thing. They just didn't agree on what "the right thing" was.

How Bitrix Composite mode works

Bitrix Composite mode is a server-side caching technique that converts a dynamic PHP page into a static HTML snapshot on the first request. Subsequent visitors receive that static file directly, bypassing PHP execution entirely.

The performance gain is real: page generation goes from 200–400ms down to 5–15ms. Under load, that difference matters.

Composite isn't a blunt instrument. It supports "holes" — components marked as always-dynamic: the cart, user authentication state, personalized prices. PHP renders those separately, then JavaScript inserts them into the static skeleton. The page is mostly static, with personalized islands filled in client-side.

This works well as long as the static skeleton doesn't get intercepted at the edge before the dynamic islands load.

What CDN caches from a Composite response — and why that breaks things

A CDN caches responses based on HTTP headers: primarily Cache-Control and Expires. If Bitrix returns a page without Cache-Control: private or no-store, most CDNs treat it as cacheable.

Here's what happens in practice: User A visits a product page. Composite generates an HTML snapshot. Bitrix returns it without a Cache-Control: private header. Cloudflare (or any CDN) caches the response on its edge node. User B makes the same request from the same geographic edge — and gets User A's cached response.

If the page contained User A's authentication cookie context — because Bitrix set Set-Cookie in that response — User B now has a session pointer that makes the page look like User A's account.

The composite skeleton doesn't contain the cart or account data directly. But AJAX requests to load those dynamic components can also be cached by CDN if the response URL doesn't vary by session cookie.

Three failure modes to know before you deploy

A CDN-Composite conflict typically shows up in one of three ways.

The cart appears empty. Composite generates the page skeleton without cart data (the cart is a dynamic component). CDN caches the skeleton. On the next visit, JavaScript tries to load the cart via AJAX to /bitrix/components/bitrix/sale.basket.basket/, but CDN returns a stale response. The cart either doesn't load or loads with someone else's state.

An authenticated user sees the login screen. CDN cached the unauthenticated version of the page (or a version with a different user's session context). The user sees "Sign in" even though they just logged in. It looks like a session timeout, but it isn't — PHP never got the request.

Personalized prices and promotions don't apply. Composite correctly marks the price block as dynamic. But CDN intercepts the AJAX call to ajax.php and returns a cached response with base prices, not the user's member discount. On high-traffic days, this affects enough users to show up in conversion data.

Diagnosing the conflict

The Composite-CDN conflict shows up in HTTP response headers before it shows up in user complaints. The fastest check: look for CF-Cache-Status in the response. A value of HIT means the response came from CDN cache. If you see HIT on pages with personalized content, that's your source.

curl -sI "https://example.com/catalog/product-slug/" | grep -E "Cache-Control|CF-Cache-Status|Set-Cookie"

If you see CF-Cache-Status: HIT alongside Set-Cookie headers in the same response, CDN has cached a response that sets authentication cookies. That's the combination that causes the "other person's data" symptom.

How to make CDN and Composite coexist

Three changes fix the Composite-CDN conflict in production without disabling either caching layer.

First, set Cache-Control: private, no-store on all responses that include session context. In Bitrix this means init.php:

AddEventHandler("main", "OnEndBufferContent", function() {
    if (CUser::IsAuthorized()) {
        header("Cache-Control: private, no-store, no-cache");
    }
});

Composite has already run by this point, so this isn't a perfect solution — but it prevents CDN from caching the response for authenticated users going forward.

Second, add cookie-based bypass rules in CDN. In Cloudflare, this is a Cache Rule: if the request contains PHPSESSID or BITRIX_SM_LOGIN cookies, bypass cache entirely. CDN coverage drops for logged-in users, but the conflict disappears.

Third, handle AJAX endpoints separately. Requests to ajax.php with session parameters should either return Vary: Cookie (so CDN caches per-cookie, not per-URL) or be excluded from CDN caching entirely. The simpler approach: route all /bitrix/ paths to bypass CDN at the cache rule level.

After applying all three, support tickets about empty carts dropped to zero over the following two weeks.

Checklist before enabling CDN on a Bitrix site with Composite

Three things to verify before CDN goes live:

  1. Check Cache-Control on authenticated pages. Run curl -sI with auth cookies — the response should include private or no-store.
  2. Configure cookie-based bypass in CDN. PHPSESSID and BITRIX_SM_* cookies must trigger bypass. This isn't optional.
  3. Audit CF-Cache-Status: HIT — it should only appear on genuinely static pages (home page without personalization, blog articles, landing pages). Check catalog pages separately.

Composite mode is a good optimization for PHP monoliths. CDN is the right tool for global latency. Used together, they need a clear boundary: what gets cached at the edge, and what always has to reach PHP.

This isn't a Bitrix bug. It isn't a CDN bug. It's an architectural decision that most deployment guides skip entirely.


More on Bitrix performance: PHP-FPM configuration under load and cache warming via cron — what actually breaks when the cache is cold.