Home › Protocols › CDN Vary Header Optimization
Protocols

CDN Vary Header Optimization: Content Negotiation Without Cache Fragmentation

By Mei-Lin Wu, Web Performance Analyst • September 14, 2026 • 9 min read
The Vary header is the most misunderstood cache control mechanism on the modern web. Used correctly, it enables elegant content negotiation. Used carelessly, it silently destroys your CDN hit ratio and inflates origin load by 10x. This guide shows how to get the negotiation without the fragmentation.

Every CDN caches based on a key, typically URL + Host. The Vary header extends that key. When your origin responds with Vary: Accept-Encoding, Accept-Language, you are instructing every edge node: do not serve this cached object to a request unless its Accept-Encoding and Accept-Language match the original request that populated the cache. If you understand that sentence, you understand 90% of Vary-related performance issues.

The challenge for performance teams is balancing correctness with efficiency. Content negotiation is essential for responsive images, internationalization, and compression, but naive Vary usage creates an explosion of cache variants. For sites handling millions of requests per day, the difference between a 95% and a 65% edge hit ratio is not academic — it is origin capacity, latency tail, and cost.

1. What Vary Actually Signals to a CDN

Unlike Cache-Control, which controls whether and how long to cache, Vary controls how many versions to cache. It is a response header that lists request headers that influenced content selection.

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Language: en
Vary: Accept-Encoding, Accept-Language
Cache-Control: public, max-age=86400
Content-Encoding: br

In this example, the CDN must maintain at least two dimensions of variants: encoding (br, gzip, identity) and language (en, es, fr). A request for Accept-Language: fr will never receive the cached en object, even if the URL is identical. This is correct behavior for language negotiation, but the CDN now stores 3 encodings × 3 languages = 9 objects per URL instead of one.

Key nuance: CDNs do not vary on headers you omit. If you serve different HTML to mobile vs. desktop but do not send Vary: User-Agent or a normalized equivalent, the CDN will cache the first variant it sees and serve it to everyone — a classic cache poisoning bug. Understanding how CDN DNS resolution routes users to edges makes this worse: the first request to a cold PoP determines what everyone in that region sees.

The Vary Cache Key Formula

Conceptually, most CDNs compute:

cache_key = hash( host + url + sorted(vary_headers_values) )

Some providers, like Fastly and Cloudflare Enterprise, allow you to customize this with Vary plus surrogate keys or custom cache key rules. Others, like CloudFront, require you to explicitly whitelist headers for forwarding — which implicitly creates a Vary-like behavior. Knowing your provider's model is critical before tuning.

Rule of thumb: Only list a header in Vary if the response body bytes would be different for different values of that header. If you only change a greeting from “Hello” to “Hola” via JavaScript, you do not need Vary: Accept-Language — let the client handle it.

2. The Cache Fragmentation Problem

Fragmentation occurs when Vary creates more cache buckets than there are meaningfully different responses. The two worst offenders are User-Agent and Cookie.

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

User-Agent fragmentation: There are over 10,000 distinct User-Agent strings in a typical day of traffic. Vary: User-Agent tells the CDN to store a separate object for each one. Even with 100 edge nodes, your hit ratio collapses because the long tail never repeats. We audited an e-commerce site using Vary: User-Agent for responsive images; their edge hit ratio was 41%. After normalization, it rose to 93% with identical user experience.

Cookie fragmentation: Vary: Cookie is even more dangerous. Since cookies often contain session IDs, CSRF tokens, or analytics identifiers that are unique per user, this effectively disables caching entirely. Never use Vary: Cookie unless you have normalized the cookie to a tiny allowlist at the edge.

Fragmentation also interacts badly with HTTP/2 and HTTP/3 connection coalescing and with tiered caching. When a shield PoP has 500 variants of the same URL, it cannot efficiently collapse requests, and origin shielding loses its benefit.

3. Optimization Strategies and Normalization

The goal is not to eliminate Vary, but to make its cardinality match your actual content variants. Three techniques cover 95% of cases:

A. Header Normalization at the Edge

Instead of varying on the raw header, have the CDN compute a normalized header and vary on that. For device detection:

# Fastly VCL example - normalize User-Agent to 3 buckets
sub vcl_recv {
  if (req.http.User-Agent ~ "Mobile|Android|iPhone") {
    set req.http.X-Device = "mobile";
  } elsif (req.http.User-Agent ~ "Tablet|iPad") {
    set req.http.X-Device = "tablet";
  } else {
    set req.http.X-Device = "desktop";
  }
}
# Origin then responds with: Vary: X-Device

Now the cache key has 3 variants instead of thousands. The same pattern works for Accept-Language: map en-US, en-GB, en-* → en at the edge, and Vary: X-Lang instead of the raw header.

B. Use Client Hints Instead of User-Agent

Modern browsers support Sec-CH-UA-Mobile, Sec-CH-Viewport-Width, and Accept-CH for responsive images. These are low-cardinality by design. Pair them with Vary: Sec-CH-UA-Mobile, Viewport-Width and you get device-appropriate images without fragmentation. This is far more cache-friendly and privacy-preserving than UA sniffing.

C. Separate URLs Instead of Varying

When variants are very different (e.g., AMP vs. canonical, or heavily personalized pricing), do not use Vary at all. Use distinct URLs or query parameters with canonical tags. For e-commerce personalization, serve a static shell cacheably and fetch price/inventory via a separate uncached API call. This keeps the main document cacheable for everyone.

StrategyWhen to UseCache EfficiencyExample Vary Header
Raw VaryLow-cardinality headers onlyHigh if ≤ 6 variantsVary: Accept-Encoding
Edge NormalizationHigh-cardinality headers with few real variantsHighVary: X-Device, X-Lang
Client HintsResponsive images, device adaptationHighVary: Sec-CH-UA-Mobile, DPR
No Vary + Client LogicPersonalization, A/B testsMediumNo Vary; fetch variant via JS
Vary: Cookie / User-AgentAlmost neverVery LowVary: Cookie — avoid

Handling Accept-Encoding Correctly

A common mistake is manually adding Vary: Accept-Encoding on every response. Most CDNs already handle this transparently: they store one compressed and one uncompressed version and negotiate automatically, without fragmenting on every gzip, deflate, br permutation. Check your provider. On Cloudflare, Fastly, and Akamai, you should not add this header for static assets — it is redundant and can double-store. Only add it if your origin does custom negotiation that the CDN does not understand, and even then, normalize to gzip vs br only.

# Nginx - correct: normalize Accept-Encoding to two values
map $http_accept_encoding $cache_encoding {
  "~*br"  br;
  "~*gzip" gzip;
  default identity;
}
add_header Vary "Accept-Encoding" always; # only if CDN not auto-handling
# Better: let CDN handle it, remove manual Vary for static assets
Security note: Incorrect Vary can leak private content. If you vary on Cookie for authenticated pages but forget Cache-Control: private, a CDN may serve one user's account page to another. See our CDN security guide for cache isolation patterns and why performance and security must be configured together.

4. CDN-Specific Implementation Patterns

Each CDN exposes Vary differently:

Fastly: Respects origin Vary by default but lets you override with beresp.http.Vary in VCL and define custom cache keys via hash_data(). Recommended: normalize in vcl_recv, then set Vary: X-Normalized-Header in vcl_fetch.

CloudFront: Does not use Vary to build keys; instead you whitelist headers in Cache Policies. Whitelisting User-Agent has the same fragmentation effect as Vary: User-Agent. Use CloudFront Functions to normalize before the cache lookup and only forward the normalized header.

Cloudflare: Honors Vary but also offers Cache Rules and Transform Rules to normalize. Enterprise customers can use Custom Cache Keys to vary on a header without forwarding it to origin, which is ideal for A/B testing: Cache Key: X-Experiment = cookie “exp” → “a|b”.

Akamai / Varnish: Fully respects Vary and supports Vary: * (uncacheable). Use EdgeWorkers or ESI to normalize. Always avoid Vary: * unless you intentionally want to disable caching.

# Cloudflare Worker - A/B test without Vary: Cookie
export default {
  async fetch(req) {
    const cookie = req.headers.get("Cookie") || "";
    const variant = cookie.includes("exp=b") ? "b" : "a";
    req.headers.set("X-Experiment", variant);
    // Cache varies only on X-Experiment (2 variants)
    return fetch(req, { cf: { cacheKey: req.url + "|" + variant } });
  }
}

For image CDNs, prefer Vary: Accept for WebP/AVIF negotiation, but normalize to image/webp, image/avif, fallback rather than the full Accept header which can contain quality values. Better yet, use content negotiation via URL suffix (/image.avif vs /image.webp) and let the edge redirect based on Accept — this keeps each format at a stable URL and avoids Vary entirely.

5. Monitoring and Validating Hit Ratio

After changing Vary, validate with real metrics, not just curl:

Set up alerts for hit ratio drops >5% week-over-week. Many teams add a Vary for a marketing experiment and forget to remove it, permanently degrading performance. Treat Vary changes like cache key schema migrations: review, test in staging with production-like traffic replay, and document the expected cardinality.

Finally, audit existing headers. Run a scan of your top 100 URLs: for url in $(cat urls.txt); do echo $url; curl -sI $url | grep -i vary; done. You will often find legacy Vary: User-Agent, Accept-Encoding, Cookie set by a CMS plugin that no longer applies. Removing one unnecessary Vary can reclaim 20 points of hit ratio overnight.

Optimized Vary is not about clever tricks — it is about aligning cache keys with real content differences. Normalize high-cardinality inputs, keep variant counts in single digits, and let the edge do the mapping. Your users get the right language, the right image format, and the right device layout, while your CDN serves 95% of requests without ever contacting origin.

Frequently Asked Questions

What does the Vary header do for CDN caching?

The Vary header tells caches which request headers were used to choose the response. A CDN creates separate cache entries for each unique combination of those header values, so a request with a different Accept-Language or encoding gets the correct variant instead of a mismatched cached copy.

Why does Vary: User-Agent cause cache fragmentation?

User-Agent has thousands of unique values across browsers and versions. Vary: User-Agent forces the CDN to store a separate object for nearly every visitor, so the cache rarely hits. Normalize User-Agent at the edge into 2–3 buckets like mobile/desktop and vary on that normalized header instead.

Is Vary: Accept-Encoding still needed with modern CDNs?

Usually no. Most CDNs automatically handle Accept-Encoding by storing gzip and Brotli variants together and serving the best match without needing an explicit Vary. Only add it manually if your origin does custom compression logic the CDN cannot detect.

How do I safely use Vary for A/B testing at the edge?

Do not use Vary: Cookie. Have an edge function read the experiment cookie and set a low-cardinality header like X-Experiment: a or b, then vary only on X-Experiment. This limits cache fragmentation to the number of test variants, typically two or three.

External References

References & Standards: Cloudflare documentation · W3C Web Performance