Home  /  CDN Architecture  /  Cache Partitioning
CDN Architecture

CDN Cache Partitioning: Vary Headers, Cache Keys, and Content Segmentation

By FlyFish NWA Editorial — Content Delivery Specialist September 12, 2026 9 min read

A CDN that caches everything under one key serves stale or broken content. One that partitions too aggressively destroys its hit ratio. Effective cache partitioning is the engineering discipline of balancing correctness and efficiency at the edge.

Modern origins rarely return a single representation for a given URL. The same /api/products endpoint may return JSON or Protobuf, compressed or uncompressed, English or Japanese, desktop or mobile HTML. If your edge cache stores only one variant per URL, clients will receive the wrong representation. Cache partitioning solves this by creating distinct cache slots for each meaningful variant while keeping the hit ratio as high as possible.

For teams running high-traffic properties, partitioning decisions directly impact origin load, Time to First Byte (TTFB), and bandwidth costs. A misconfigured Vary header can double origin traffic overnight. An overly broad cache key can serve private data to the wrong user. This guide covers the three layers of partitioning every CDN engineer must master.

1. Why Cache Partitioning Matters

At its core, a CDN cache is a key-value store. The cache key is typically derived from the request URL, but the value — the cached response — depends on many other dimensions: request headers, device class, language, authentication state, and even edge location. Without partitioning, the cache suffers from collisions.

Consider an image optimization service. A request for /hero.jpg from a Chrome browser that supports image/webp should return WebP. The same URL from Safari 14 should return JPEG. If the first response is cached without partitioning on Accept, the second client receives an unsupported format and shows a broken image. The reverse is also true: serving JPEG to a WebP-capable client wastes 25-35% bandwidth.

Partitioning also affects performance observability. When you segment correctly, your CDN performance metrics like cache hit ratio become meaningful per segment. A 92% overall hit ratio can hide a 45% hit ratio for your API tier if you are not measuring by partition. Effective segmentation lets you isolate low-performing content types and tune them independently, which is essential for any serious monitoring and observability strategy.

Core Principle: Partition only on dimensions that change the response body or critical response headers. Every additional key dimension fragments your cache and reduces hit ratio. If the origin ignores a header, don't include it in the key.

2. The Vary Header: HTTP's Native Partitioning

The Vary response header is defined in RFC 9110 as the standard way for an origin to tell caches which request headers affect content selection. When an origin responds with Vary: Accept-Encoding, Accept-Language, it instructs any downstream cache — browser, CDN, or proxy — to store separate variants keyed by those request headers.

Performance Comparison: Edge vs Origin Processing
Metric Edge Processing Origin Processing Improvement
Latency (p50)8–15 ms120–300 ms15–20x faster
Cache Hit Ratio85–95%N/AReduces origin load
TLS Handshake1–5 ms (nearby PoP)50–150 ms10–30x faster
Bandwidth Cost$0.01–0.04/GB$0.05–0.12/GB3–5x cheaper
Availability99.99% (multi-PoP)99.9% (single DC)10x fewer outages

CDNs respect Vary automatically, but how they implement it varies. Most edge platforms normalize the listed headers (lowercasing, trimming, sorting) and append their values to the internal cache key. A request with Accept-Encoding: gzip and one with Accept-Encoding: br will result in two objects, even though the URL is identical.

When Vary Works Well

Vary is ideal for content negotiation that is already standardized:

  • Accept-Encoding: For compression. Most CDNs handle this automatically and may even compress on the fly at the edge. You should almost always include this. See our guide to CDN image optimization for how Accept and Accept-Encoding interact.
  • Accept-Language: For localized pages or API messages. Use only if you truly localize at the origin.
  • Accept: For API versioning or format negotiation (JSON vs XML).

When Vary Becomes Dangerous

The most common mistake is Vary: User-Agent. User-Agent strings have thousands of variants. Partitioning on the full string effectively creates a cache miss for nearly every request, collapsing your hit ratio to single digits and turning your CDN into an expensive proxy. Never use Vary: User-Agent raw. If you need device-specific variants, normalize at the edge first.

Another pitfall is Vary: *, which tells caches not to use the response for any subsequent request — effectively disabling caching. Some misconfigured origins add this when they set cookies, which is rarely intended.

Example — Correct vs. Problematic Vary Headers # Good: Specific, low-cardinality negotiation HTTP/1.1 200 OK Content-Type: text/html Content-Encoding: br Vary: Accept-Encoding, Accept-Language Cache-Control: public, max-age=86400 # Bad: High-cardinality, cache-busting HTTP/1.1 200 OK Content-Type: text/html Vary: User-Agent, Cookie, Referer Cache-Control: public, max-age=86400 # Will have <10% hit ratio

If you must vary on User-Agent, do it via device classification at the edge. Cloudflare, Fastly, and Akamai all offer device detection that maps thousands of UA strings into 3-4 buckets (desktop, mobile, tablet, bot) before the cache lookup. This keeps cardinality low while preserving correctness.

3. Cache Key Design: Beyond the URL

While Vary is origin-driven, cache keys are CDN-driven. Most providers let you explicitly define what constitutes a cache key, overriding or extending Vary. This is where you have the most control — and the most risk.

A default cache key is usually host + path + query string. Customization lets you include or exclude components:

Key Component Include When Risk if Misused Example
Query String It changes content (pagination, filters) Cache fragmentation from tracking params (utm_*) ?page=2 vs ?utm_source=x
Accept / Accept-Language Origin does content negotiation Over-partitioning if values are diverse Accept: image/webp
Cookie / Auth Almost never for public cache Private data leakage, 0% hit ratio session_id
Device Class Origin returns different HTML per device Hit ratio drop if too many buckets X-Device: mobile
Geo / Edge Location Geo-personalized content (pricing, compliance) Unnecessary duplication for global content X-Geo: EU

Normalization is Non-Negotiable

Before adding a header to the key, normalize it. Sort query parameters alphabetically, lowercase header values, strip irrelevant cookies, and collapse whitespace. Two requests for /search?q=cdn&lang=en and /search?lang=en&q=cdn should hit the same object.

Similarly, strip marketing parameters from the key while preserving them for analytics. Most CDNs offer query string allowlists or denylists:

Fastly VCL — Custom Cache Key with Normalization sub vcl_hash { # Base key: host + path set req.hash += req.http.host; set req.hash += req.url.path; # Allowlist only meaningful query params if (querystring.get(req.url, "page")) { set req.hash += querystring.get(req.url, "page"); } if (querystring.get(req.url, "lang")) { set req.hash += querystring.get(req.url, "lang"); } # utm_* params are ignored for caching # Normalized device class (set earlier in vcl_recv) set req.hash += req.http.X-Device-Class; # mobile | desktop | tablet # Language variant set req.hash += req.http.Accept-Language; return (lookup); } sub vcl_recv { # Normalize device: map 1000s of UAs to 3 buckets if (req.http.User-Agent ~ "Mobile|Android|iPhone") { set req.http.X-Device-Class = "mobile"; } else { set req.http.X-Device-Class = "desktop"; } # Remove Cookie for anonymous content to allow caching if (req.url.path ~ "^/static/") { unset req.http.Cookie; } }
Cloudflare / Nginx — Cache Key Excluding Tracking Params # Cloudflare Cache Rule: Cache Key -> Custom # Include: Host, Path, Query (allowlist: page, lang, format) # Exclude: utm_source, utm_medium, fbclid # Nginx proxy_cache_key proxy_cache_key "$scheme$host$uri$arg_page$arg_lang$http_accept_language$http_x_device"; # Note: $args would include all query params — avoid it

A critical security note: never include Cookie or Authorization in the cache key for shared caches unless you are building a private, per-user cache. Doing so will cache authenticated responses and serve them to unauthenticated users. For personalized content, use Cache-Control: private or Vary: Cookie with edge authentication, and consider how your DDoS protection layer interacts with authenticated cache bypasses.

4. Content Segmentation Strategies

Cache keys handle variants of a single URL. Content segmentation handles different caching policies for different types of content. Not all content should be cached the same way, or at all.

Segment your content into tiers and apply distinct partitioning and TTL strategies to each:

Tier 1: Static Immutable Assets

Hashed assets like /assets/app.a3f9c1.js or /images/logo.svg should have the longest TTL (1 year) and the simplest key (URL only). Use content hashing so each deploy creates a new URL — no purging needed. Partitioning on headers is wasteful here; a JPEG is a JPEG regardless of language.

Tier 2: Semi-Static Content

Marketing pages, blog posts, product descriptions. Cache for minutes to hours. Partition on Accept-Encoding and Accept-Language if localized, and optionally on device class if you serve adaptive HTML. Use stale-while-revalidate to hide origin latency. Your DNS resolution path also affects how quickly users reach the right edge for these pages, so keep TTLs aligned.

Tier 3: Dynamic / API Responses

API endpoints, search results, cart data. Cache for seconds or not at all. Partition carefully: include only query params that affect the result set, exclude session tokens. For GraphQL, consider caching at the edge by normalizing the POST body into a cache key — many CDNs now support this via workers.

For APIs, prefer surrogate keys (cache tags) over URL-based purging. Tag responses with Surrogate-Key: product-123 category-shoes and purge by tag when data changes. This avoids the thundering herd problem of purging thousands of URLs.

Origin — Setting Surrogate Keys and Tiered Cache-Control # Static asset — immutable, 1 year Cache-Control: public, max-age=31536000, immutable Surrogate-Key: static assets # Product page — 5 min at edge, stale for 1 hour Cache-Control: public, max-age=300, stale-while-revalidate=3600 Surrogate-Key: product-123 product-list Vary: Accept-Encoding, Accept-Language # API — 10s, must revalidate, partitioned by query Cache-Control: public, max-age=10, must-revalidate Surrogate-Key: api-products Vary: Accept

Segmentation also enables different shielding strategies. Place static assets on all edges globally, but shield dynamic API traffic through a single regional origin shield to improve coalescing and reduce origin load.

5. Pitfalls, Monitoring & Tuning

Partitioning errors are silent until they cause incidents. Build guardrails:

  • Measure per-partition hit ratio. Don't rely on a global average. Break down by content tier, device class, and language. A sudden drop in one partition often signals a new header or query param leaking into the key.
  • Enforce a cardinality budget. Limit custom key dimensions to 3-4 low-cardinality values. If a dimension has >20 unique values per hour, it probably shouldn't be in the key.
  • Log cache status. Emit X-Cache: HIT/MISS and X-Cache-Key (hashed) to your logs. Correlate misses with the normalized key to spot fragmentation.
  • Test with cache poisoning in mind. Verify that a request with a malicious X-Forwarded-Host or extra cookie doesn't create a poisoned variant that is served to others.
  • Purge by tag, not by URL. When you partition heavily, a single logical page may have 12 cached variants. Purging by surrogate key invalidates all variants atomically.

Finally, revisit your partitioning quarterly. As you add languages, image formats (AVIF), or A/B test buckets, your key design must evolve. What was correct at 3 variants can become pathological at 30. The goal is not to cache everything — it is to cache the right variants efficiently while keeping the origin authoritative for the rest.

Correct partitioning is a force multiplier: higher hit ratios mean lower origin costs, faster TTFB, and more headroom for your edge security controls to operate without adding latency.

Frequently Asked Questions

What is the difference between Vary and a custom cache key?

Vary is an origin-declared instruction that tells any cache which request headers create different variants. A custom cache key is a CDN-specific configuration that explicitly defines the key composition, often normalizing or mapping headers before they enter the key. Use Vary for standards-based negotiation (Accept-Encoding) and custom keys when you need normalization, allowlisting, or device classification that Vary alone cannot provide.

How does over-partitioning affect cache hit ratio?

Each additional key dimension multiplies the number of cache objects. Adding full User-Agent (thousands of values) or an unbounded query string can fragment a single URL into thousands of objects, dropping hit ratio from 90%+ to under 10%. This increases origin load, latency, and egress costs. Keep dimensions to 2-4 low-cardinality values (e.g., 3 device classes, 5 languages) and strip irrelevant parameters.

Should you include User-Agent in cache keys?

Never include the raw User-Agent string. Its high cardinality destroys cache efficiency. If you need device-specific rendering, classify the User-Agent at the edge into a small set of buckets (mobile, desktop, tablet) and include only that normalized bucket in the key. Most CDN providers offer built-in device detection for this purpose.

How do CDNs handle Vary: Accept-Encoding automatically?

Most modern CDNs handle Accept-Encoding outside of normal Vary logic. They store one uncompressed origin response and compress it at the edge to gzip, Brotli, or Zstandard based on the client's Accept-Encoding, or they store 2-3 compressed variants transparently. You typically do not need to configure this manually, but you should ensure your origin does not send Vary: * and that you forward Accept-Encoding correctly to the origin if needed.

References & Standards: W3C Web Performance · RFC 7234