CDN Purge and Invalidation
Caches exist because content rarely changes. But when content does change, you need a way to tell every POP "the copy you have is stale; please fetch a new one." This is purging, and it is one of the operational primitives that determines how dynamic a CDN-fronted site can be. The mechanisms range from per-URL invalidation (slow, expensive at scale) to tag-based purging (fast, scales to millions of related objects in one operation) to cache busting via versioned URLs (the cleverest approach — change the URL instead of the cache). This guide explains each, when to use which, and the propagation timing realities.
The four ways to invalidate a cache
| Method | Granularity | Propagation | Best for |
|---|---|---|---|
| URL purge | Specific URL(s) | Fast (sub-second to seconds) | One-off updates to specific resources |
| Tag-based purge | All objects with a tag | Fast (sub-second to seconds) | Bulk invalidation tied to data changes |
| Soft purge | URL or tag, mark stale | Immediate (revalidation happens lazily) | Graceful staleness during updates |
| Versioned URLs | By URL change | Instant (new URL = no cache) | Static assets with build-time versioning |
URL purge: the simplest model
URL purge tells the CDN to invalidate the cached copy of one or more specific URLs. The CDN propagates the invalidation to every POP. Subsequent requests trigger a fresh origin fetch.
API call examples:
# Cloudflare
curl -X POST 'https://api.cloudflare.com/client/v4/zones/{id}/purge_cache' \
-H 'Authorization: Bearer {token}' \
-d '{"files":["https://example.com/page1","https://example.com/page2"]}'
# Fastly
curl -X POST 'https://api.fastly.com/service/{service_id}/purge/{url}' \
-H 'Fastly-Key: {token}'
# CloudFront (called 'invalidation')
aws cloudfront create-invalidation \
--distribution-id {dist_id} \
--paths "/page1" "/page2"
Limits:
- Cloudflare: Up to 30 URLs per API call; 1000 URLs per minute (Free), more on paid plans.
- Fastly: Effectively unlimited; instant purge is the default purge mechanism.
- CloudFront: First 1000 paths/month free; $0.005 per path after. Slower propagation (60-180 sec).
URL purge works well for small numbers of updates. It breaks down when one origin change affects many cached URLs — e.g., editing a category page that has cached representations across pagination, filters, and language variants.
Tag-based purge: scaling to many objects
The origin attaches one or more tags to each cached response via a special header:
# Cloudflare
Cache-Tag: product-12345, category-shoes, brand-nike
# Fastly (uses Surrogate-Key)
Surrogate-Key: product-12345 category-shoes brand-nike
# Akamai
Edge-Cache-Tag: product-12345 category-shoes brand-nike
The CDN remembers which cached objects have which tags. Later, a single API call purges every object with a given tag across every POP:
# Cloudflare
curl -X POST 'https://api.cloudflare.com/client/v4/zones/{id}/purge_cache' \
-H 'Authorization: Bearer {token}' \
-d '{"tags":["product-12345"]}'
# Fastly
curl -X POST 'https://api.fastly.com/service/{service_id}/purge/product-12345' \
-H 'Fastly-Key: {token}'
This is the dominant approach for content management. When a product is edited, the application API purges product-12345 and every cached page that references that product — product detail page, category listing, search results, recommendation widgets — is invalidated in one operation, without your code knowing which URLs were cached.
Tagging is application logic — the origin decides what tags to apply. Pattern: tag by domain object (entity ID), by category, by language, by user segment. Avoid over-fragmenting (tagging by random UUIDs gives you no purge leverage).
Soft purge: graceful invalidation
Hard purge removes the cached object. The next request is a cache miss, paying the full origin fetch cost. Soft purge instead marks the object stale — it stays in cache but is treated as expired.
Combined with stale-while-revalidate in the origin's Cache-Control headers, the behavior becomes:
- Object is marked stale by soft purge.
- Next request finds stale object.
- CDN serves the stale object to the user immediately.
- In the background, CDN fetches fresh content from origin.
- Stale object is replaced with fresh one for subsequent requests.
The user sees a fast response (stale content) and never waits for origin. The next user after revalidation sees fresh content. The trade-off: users between purge and revalidation see stale data. For most use cases, sub-second staleness during revalidation is invisible.
Fastly's instant purge is soft purge by default. Cloudflare offers soft purge as an option on paid plans.
Versioned URLs: the no-purge approach
The cleverest cache strategy is to never need a purge. Instead, when content changes, change the URL. The old URL stays cached (and irrelevant). The new URL has no cache entry, so the first request fetches fresh content.
Patterns:
Content hash in filename
/assets/app-a8f3b2.css ← v1
/assets/app-c4d2e1.css ← v2 (content changed → new hash → new URL)
Used by every modern frontend bundler (webpack, esbuild, Vite, Turbopack). The HTML references the current hashed URL; users always get the right version. Old URLs can stay cached for a year — they will never have wrong content because the URL itself is the version key.
Build ID in path
/2026-05-25-build-1234/assets/app.css
Same idea, less granular. Whole-build versioning. Works for SPAs that deploy as a unit.
Query string version
/assets/app.css?v=1234
/assets/app.css?v=1235
Works but less elegant. Older CDNs sometimes ignore query strings in cache keys by default; configure carefully.
For HTML pages (which can't easily have versioned URLs because users bookmark them), versioned URLs work for embedded assets but not for the HTML itself. The HTML uses short TTLs + purges; embedded assets use one-year TTLs + versioning.
Propagation timing realities
Purges propagate from the API endpoint to every POP. Different CDNs have very different propagation characteristics:
| CDN | Documented purge time | Notes |
|---|---|---|
| Fastly | 150 ms global P95 | Instant purge is the default; tags or URLs |
| Cloudflare | Under 5 seconds global | URLs sub-second; tags can take a few seconds at scale |
| Akamai | 5-15 seconds | Fast Purge; Enhanced Purge is faster but optional |
| CloudFront | 60-180 seconds | Invalidation API; rate-limited and billable beyond free tier |
| Vercel | Under a second | Tag-based via the Data Cache API |
| Bunny.net | ~30 seconds | Smart Purge propagates across POPs |
CloudFront's slow invalidation is its biggest operational weakness. For workloads needing fast purge, prefer Cloudflare, Fastly, or use versioned URLs to avoid invalidations entirely.
Common purge patterns by application type
E-commerce
- Product changes: Tag-based purge on
product-{id}. Triggers from the product management API or database CDC. - Category page changes: Tag on
category-{slug}for category pages. - Inventory changes: Often handled at the application layer (fetch live) rather than via cache, because inventory changes rapidly.
- Static assets: Versioned URLs.
News / publishing
- Article publish/edit: Tag on
article-{id}. Purges detail page + section pages + homepage if featured. - Homepage: Tag on
home. Purged when any featured content changes. - Static assets: Versioned URLs with year-long TTL.
SaaS dashboard
- Per-user data: Don't cache, or cache with very short TTL + user-specific cache key. No purges needed.
- Shared organization data: Tag on
org-{id}for org-wide invalidation. - App bundle: Versioned URLs.
Blogs
- Post edits: Tag on
post-{slug}. - Author updates: Tag on
author-{id}. - Site-wide configuration changes: Tag on
site-config, purges everything.
Webhook integration
Modern purge patterns trigger from data changes via webhooks:
CMS publish → webhook → purge worker → CDN API
Database update → CDC → purge service → CDN API
Build complete → CI step → purge homepage tag → CDN API
This decouples the application code from the CDN — the application doesn't know which URLs are cached; it just announces "this thing changed" via a tag, and a purge worker handles the CDN call. Useful for evolving the cache strategy without touching application code.
What to never purge
Some content should be cached with a TTL and never explicitly purged:
- Versioned static assets. URL changes when content changes. No purge needed.
- Content with strict TTL and tolerance for staleness. Stock tickers cached for 30 seconds. Just let them expire.
- API responses with explicit timestamps. If the response includes "as of 2026-05-25T10:30:00Z", clients can decide whether to trust it without needing the cache to enforce freshness.
Purging should be reserved for content that is genuinely "needs to be fresh now" — admin edits, breaking news, security advisories. Routine content freshness should use short TTLs + stale-while-revalidate.
Frequently Asked Questions
How fast does a CDN purge propagate?
Modern CDNs target sub-second purge propagation globally. Fastly's instant purge is documented at 150 ms global P95. Cloudflare claims under 5 seconds for tag-based purge to all POPs. AWS CloudFront invalidation typically takes 60-180 seconds. Older Akamai purge mechanisms can take 5-15 seconds. For most use cases, sub-second purge means users see fresh content essentially immediately; CloudFront's slower invalidation is the main outlier.
What is a soft purge?
A soft purge marks the cached object stale rather than removing it. The next request triggers a background revalidation with origin — fresh content replaces stale once retrieved — but users can be served the stale copy in the meantime via stale-while-revalidate. This combines purge with graceful degradation: requests during the brief revalidation window get stale content instead of cache miss latency. Fastly pioneered this; most modern CDNs support it.
What is tag-based purge?
Tag-based purge invalidates all cached objects that have a particular tag, set by the origin via a Cache-Tag response header (Cloudflare), Surrogate-Key (Fastly), or similar. Origin tags objects by what they relate to — e.g., a product page might be tagged "product:12345" and "category:shoes". Purging the "product:12345" tag invalidates every cached representation of that product across all POPs in one operation. This avoids needing to enumerate every URL that might cache a piece of content.
Why does my CloudFront invalidation take 90 seconds?
CloudFront invalidation is asynchronous and has to propagate to 600+ edge locations. The typical time is 60-180 seconds. It is also rate-limited — the first 1000 paths per month are free; beyond that, $0.005 per path. For frequent invalidations, AWS recommends versioned URLs (changing the URL forces a fresh fetch without an explicit invalidation) rather than relying on the invalidation API.
What is cache busting via versioned URLs?
Cache busting changes the URL when content changes — appending a version, content hash, or build ID. Old URL ("app.js?v=42") remains cached; new URL ("app.js?v=43") fetches fresh. No purge is needed because the new URL has no cached copy. This is the canonical approach for static assets: hash-named CSS and JS bundles get one-year cache TTLs because the URL itself encodes the version.
Related Guides
More From This Section
All CDN & Edge Guides
How CDNs work, cache headers, anycast, edge functions, and security.
Anycast vs GeoDNS
Anycast and GeoDNS compared — how each routes users to CDN points of presence, BGP convergence, GeoDNS resolver…
Cache Hit Ratio Explained
What cache hit ratio actually measures, the difference between request and byte hit rate, and the configuration changes…
Run a Speed Test
Measure download, upload, ping, and jitter in your browser.