CDN cache keys and invalidation: response variants, private data and safe releases
Design CDN cache keys around response identity. Explain origin forwarding, private-response risks, TTLs, versioned assets and why invalidation alone cannot fix a wrong cache key.
TL;DR: Requests may share a cached response only when the response is valid for all of them. Define that equivalence first, then choose expiration and invalidation behavior. Forwarding a value to the origin does not necessarily make it part of the cache key.
A cache key decides which requests are equivalent
A CDN stores an object under a key derived from configured request attributes. If language changes the representation but language is absent from that key, a later request can receive the earlier language's cached response. If a tracking parameter never changes the representation but enters the key, it can fragment the cache unnecessarily.
CloudFront distinguishes cache policies from origin request policies. Values can be forwarded to the origin without joining the cache key. Its cache-key documentation explains this separation. Review the actual CDN configuration rather than assuming every header sent upstream creates a separate cached variant.
The safety check in the diagram is a design responsibility implemented through the application and CDN policies. A CDN does not infer your tenant boundaries from the meaning of a cookie.
Work through a localized catalogue
Suppose an anonymous catalogue has English and Hindi representations selected by a normalized lang query value. Requests /catalog?lang=en&campaign=a and /catalog?lang=en&campaign=b may share content if campaign tracking does not alter the response. The Hindi representation must use a different key.
This local model demonstrates the equivalence decision, not a provider configuration:
from urllib.parse import urlsplit, parse_qs
def public_catalog_key(url):
parsed = urlsplit(url)
query = parse_qs(parsed.query)
language = query.get("lang", ["en"])[0]
if language not in {"en", "hi"}:
raise ValueError("unsupported language")
return parsed.path, language
assert public_catalog_key("/catalog?lang=en&campaign=a") == public_catalog_key("/catalog?lang=en&campaign=b")
assert public_catalog_key("/catalog?lang=en") != public_catalog_key("/catalog?lang=hi")
The origin must use the same normalization contract. If duplicate query values are interpreted differently by the key builder and application, an attacker or an ordinary malformed request can produce a mismatch. Reject ambiguous inputs or normalize them consistently at an agreed boundary.
Keep private responses outside a shared public cache
A personalized account page needs an explicit access and caching design. Adding a session cookie to a key is not a complete authorization system, and creates many low-reuse variants. For ordinary private pages, disable shared caching and verify behavior across authenticated and anonymous requests.
CloudFront has a significant TTL caveat: a positive minimum TTL can cause caching despite origin no-cache, no-store or private directives. The cache-policy reference documents that behavior. Check both the origin's headers and the CDN policy. Testing only the origin bypasses the configuration that could expose the response.
| Response class | Suitable starting design | Verification |
|---|---|---|
| Public versioned asset | Long cache lifetime under unique URL | Bytes match the versioned identity |
| Public localized page | Key includes normalized variant | Alternating locales stay correct |
| Personalized account data | Shared-cache bypass | Different viewers never receive another body |
| Frequently changing public pointer | Short lifetime or controlled purge | Update propagation meets the requirement |
Prefer new asset names for new bytes
Publish immutable assets under a content-derived or release-specific path, then update the HTML reference. Keep older assets available while old HTML can still be served. Replacing bytes behind a long-lived URL leaves browser and intermediary caches with competing versions.
Invalidation removes selected cached objects from the CDN according to its behavior. It does not recall content already stored by a browser or repair a missing variant in the cache key. A purge after a language mix-up can temporarily hide the symptom until the wrong representation is cached again.
Broad invalidation also increases origin traffic while caches refill. Estimate the working set and origin headroom before a large release. Pre-warming a few popular public objects may help predictable demand, but does not guarantee every edge location is warm or justify caching private data.
Self-check: the origin receives X-Tenant and returns tenant-specific content, but the CDN excludes it from the key. Can forwarding alone make this safe? No. Requests can collide on the same stored representation. Redesign shared-cache behavior around authorization and response identity, then alternate tenants in a controlled test. A high hit ratio is meaningless if the hits return the wrong customer's data.