Invalidation, purging and what not to cache

How to retire a cached copy quickly, why fingerprinted URLs beat purging, and the responses that must never be stored.

Getting rid of a stale copy

There are three ways to invalidate: purge by URL, purge by tag or surrogate key, and purge everything. Purging one URL is precise; tag-based purging is what you want when a template change affects thousands of pages; purge-all is the emergency lever that also empties your cache and hammers the origin.

# purge one URL (provider-specific API shape)
curl -X POST "https://api.cdn.example/purge" \
  -H "Authorization: Bearer $CDN_TOKEN" \
  -d '{"urls":["https://example.com/assets/app.js"]}'

# tag the response at the origin, then purge by tag after a content update
# Surrogate-Key: article-1042 list-home
curl -X POST "https://api.cdn.example/purge" \
  -H "Authorization: Bearer $CDN_TOKEN" \
  -d '{"tags":["article-1042"]}'
  • Purge is not instant everywhere: propagation takes seconds to a minute, and some edges may still hold a copy briefly.
  • Purging is a poor substitute for correct cache keys and a short TTL on HTML.
  • Fingerprinted assets need no purging at all - a new filename is a new cache entry, and the old one expires harmlessly.
  • After a purge, expect a burst of requests to the origin. Stagger large purges if the origin is not sized for it.

What must not be cached

ResponseWhy it must not be sharedCorrect header
Logged-in pagesContains another user's data if served to the wrong personprivate, no-store
Cart and checkoutSession-specific state that changes on every actionprivate, no-store
Anything with Set-CookieThe cookie would be handed to other visitorsprivate and strip cookies at the edge
Authentication endpointsCached tokens or redirects replay a previous loginno-store
Draft or preview URLsUnpublished content becomes public and indexableno-store plus access control
Personalised API responsesPrices and entitlements differ per accountprivate, max-age=0
Error responsesA cached 500 keeps serving the failure after the fixno-store for 5xx
# origin: be explicit rather than relying on defaults
location /account/ {
  add_header Cache-Control "private, no-store" always;
  add_header X-Robots-Tag "noindex" always;
}

# edge: strip cookies from cacheable assets so they are not keyed on them
location /assets/ {
  proxy_hide_header Set-Cookie;
  add_header Cache-Control "public, max-age=31536000, immutable" always;
}
⚠️
Cache configuration is a security boundary, not only a performance setting. A shared cache holding one visitor's authenticated response is a data leak, and the leaked URL can be indexed by search engines before anyone notices.

A strategy that avoids purging

  1. Give every asset a content hash in its filename, and cache it for a year as immutable.
  2. Serve HTML with a short s-maxage so a deploy is live within minutes without any purge.
  3. Tag long-lived, frequently updated content (a product, an article) so a single change can be purged by key.
  4. Keep a documented purge procedure with a token that is stored in a secret manager, not in a script on someone's laptop.
  5. Watch the hit ratio after each change; a sudden drop usually means a cache key was altered by accident.

Sites that follow this pattern barely use purge at all - stale content is short-lived by construction, and changed content arrives on a fresh URL.

FAQ

How long does a purge take?
Usually seconds, but never assume it is atomic worldwide. Verify with curl -sI and an Age header of 0 from more than one location before declaring it done.
Why do users still see the old page after a deploy?
The browser cached the HTML. Lower max-age on HTML (or use no-cache), keep assets fingerprinted, and issue a purge for the entry points.

Cache headers and cache keys Build and deploy workflows

Last refreshed 2026-09-18.