Cache key and Vary testing
The cache key is the string a CDN uses to look up a cached response. Two requests with the same cache key get the same cached response. The CDN caches requests with different keys separately. Cache-key design matters. A key that is too narrow fragments the cache and wrecks the hit ratio. A key that is too broad sends one user’s personalised or content-negotiated response to another user, which is both a correctness and a security problem.
What makes up a cache key
Section titled “What makes up a cache key”By default, most CDNs build the cache key from:
- Scheme (
https://) - Host (
cdn.example.com) - Path (
/static/app.js) - Query string (optionally, and often configurable)
Extensions to the key depend on CDN configuration:
Varyheader: tells the CDN to add specific request headers to the key.- Custom cache key rules: CloudFront, Fastly, and Cloudflare all let you add cookies or custom headers to the key, or strip query parameters from it.
- Normalisation: some CDNs normalise query-parameter order. Others do not, so they cache
/api?a=1&b=2and/api?b=2&a=1separately.
Query-string cache-key testing
Section titled “Query-string cache-key testing”Query strings are the most common cause of unintended cache fragmentation. Marketing tools add analytics or session parameters (e.g. ?utm_source=email&_ga=2.12345). The CDN then caches every URL variant separately, and the CDN stops helping for that resource.
Test for query-string fragmentation
Section titled “Test for query-string fragmentation”Send the same URL with different query strings and check whether the cache serves all responses:
scenarios: query-string-test: requests: # Clean URL — establishes cache entry - label: clean-url url: /static/app.js method: GET
# UTM parameter — should hit same cached entry if CDN ignores UTM params - label: utmed-url url: /static/app.js?utm_source=email&utm_campaign=launch method: GET assert: - contains: subject: headers value: 'X-Cache: HIT' # should be a cache hit if UTM is stripped
# Session parameter — should NOT vary the cache if caching is anonymous - label: session-url url: /static/app.js?session_id=abc123 method: GET assert: - contains: subject: headers value: 'X-Cache: HIT'If the UTM-parameterised URL returns a MISS but the clean URL returns a HIT, your CDN includes query parameters in the cache key. Configure the CDN to ignore tracking parameters for static assets.
Test for required query-string differentiation
Section titled “Test for required query-string differentiation”For API responses that use query parameters to vary content, the opposite holds. The CDN must cache each query variation separately:
scenarios: api-cache-key-test: requests: # Different page numbers must return different cached responses - label: page-1 url: /v1/catalog?page=1&limit=20 method: GET assert: - equals: subject: http-code value: '200'
- label: page-2 url: /v1/catalog?page=2&limit=20 method: GET assert: - equals: subject: http-code value: '200'After you warm both URLs, verify that each returns its own cached response, and that page=1 does not get the same body as page=2.
Vary header testing
Section titled “Vary header testing”The Vary response header tells the CDN which request headers to add to the cache key. With Vary: Accept-Encoding, the CDN caches compressed and uncompressed versions separately. With Vary: User-Agent, every browser variant gets its own cached copy, which all but disables the cache.
Common Vary misconfigurations
Section titled “Common Vary misconfigurations”| Vary value | Impact |
|---|---|
Vary: Accept-Encoding | Correct for text assets. The CDN caches compressed content separately from uncompressed. |
Vary: Accept-Language | May fit internationalised responses, but fragments the cache heavily. |
Vary: Cookie | Catastrophic for any public-facing resource. Every user’s cookies produce a unique cache entry. |
Vary: User-Agent | Catastrophic. Thousands of User-Agent strings mean almost nothing gets cached. |
Vary: * | Disables caching entirely for that response. Usually a configuration error. |
Detecting Vary problems in MaxoPerf
Section titled “Detecting Vary problems in MaxoPerf”Write a test that sends requests with different Accept-Language or User-Agent values. Assert that the CDN serves the same cacheable asset as a hit whatever those header values are (if the content is the same across languages or clients):
scenarios: vary-test: requests: - label: default-agent url: /static/app.js method: GET headers: User-Agent: 'Mozilla/5.0 (compatible; load-test)' assert: - contains: subject: headers value: 'X-Cache: HIT'
- label: different-agent url: /static/app.js method: GET headers: User-Agent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)' assert: - contains: subject: headers value: 'X-Cache: HIT'If different-agent returns a MISS when default-agent returned a HIT, the CDN has Vary: User-Agent active on that response. For a static asset, that is a critical misconfiguration.
Cache poisoning awareness
Section titled “Cache poisoning awareness”Cache poisoning is a security vulnerability. An attacker gets malicious content stored in the CDN cache, and the CDN serves it to other users. MaxoPerf is a load testing tool, not a security scanner. Still, design your CDN load tests with cache poisoning vectors in mind so you do not trigger them by accident.
Common cache poisoning patterns to be aware of
Section titled “Common cache poisoning patterns to be aware of”- Unkeyed request headers: if a CDN includes an
X-Custom-Headerin cached responses but not in the cache key, an attacker who controls that header can poison the cache. Do not send unusual headers to production CDNs unless you are testing for exactly this. - URL normalisation differences: some CDNs normalise paths (
//static/app.js→/static/app.js) before caching but serve responses that reflect the original path. A request for//script.jscould get the cached response for/script.js, or could create a new cache entry at an unexpected URL. - Host header injection: a manipulated
Hostheader can get cross-site content cached. Always send the correctHostheader for the CDN endpoint in MaxoPerf load tests.
Testing cache-key normalisation
Section titled “Testing cache-key normalisation”Some CDNs normalise query-parameter order before building the cache key. Others do not. If your CDN does not, it caches /api?a=1&b=2 and /api?b=2&a=1 separately, which halves the hit ratio for that endpoint.
Test for this:
scenarios: normalisation-test: requests: # Forward order - label: params-ab url: /v1/catalog?category=shoes&limit=20 method: GET
# Reversed order — should hit the same cache entry if normalised - label: params-ba url: /v1/catalog?limit=20&category=shoes method: GET assert: - contains: subject: headers value: 'X-Cache: HIT'Do / don’t
Section titled “Do / don’t”Do:
- Audit
Varyheaders on every cached response type.Vary: Accept-Encodingis correct.Vary: User-AgentorVary: Cookieon public assets is almost always wrong. - Test with both clean URLs and query-parameterised URLs to confirm the CDN is stripping (or including) query parameters as intended.
- Write down your CDN’s cache-key rules. Undocumented variations cause most cache-fragmentation surprises.
Don’t:
- Use
Vary: *on any response that should be cached. It prevents caching entirely. - Assume CDN cache-key defaults are correct for your use case. Every CDN has different default query-string handling.
- Ignore
Varyheader misconfigurations in load test results. A suspiciously low hit rate with high TTFB on a widely shared asset is often aVaryproblem.
Where to go next
Section titled “Where to go next”- Cache hit/miss testing: measure how cache-key decisions affect hit ratio.
- Cache invalidation and purge testing: cache-key design sets the scope and blast radius of purges.
- CDN testing do and don’t: the full rule set, including cache poisoning and
Varyhygiene. - Origin shield and offload testing: cache-key fragmentation directly lowers the origin offload percentage.