Skip to main content
CDN Cache Invalidation: Strategies That Don't Break in Production

CDN Cache Invalidation: Strategies That Don't Break in Production

KU
· Founder & Chief Editor
• 8 min read•Updated •39 views

"There are two hard problems in computer science." We've worked on the cache-invalidation one for a while. The patterns that hold up at scale and the ones that look clean and aren't.

Key takeaways

  • Avoid purging wherever you can.
  • Content-hash static asset filenames and cache them for a year as immutable, give HTML and APIs short s-maxage values with stale-while-revalidate, and reserve explicit purges for urgent edits.
  • Use cache tags, now on CloudFront, Cloudflare and Fastly, when one change touches many URLs.

The invalidation strategy that holds up is to need as few purges as possible: content-hash static filenames and cache them for a year with immutable, give HTML and API responses a short s-maxage plus stale-while-revalidate, and purge only for urgent edits. When one change touches many URLs, purge by cache tag instead of by path.

Verified against

Amazon CloudFront Developer Guide (path and tag invalidation, invalidation billing), Cloudflare purge-by-tag docs, and Fastly surrogate key docs, against RFC 9111 and RFC 5861. Checked October 2026.

The "send a purge" approach works until you hit its edges: purges take time to propagate, every provider rate-limits or bills them, and a forgotten URL keeps serving stale content.

The shapes of the problem

Content falls into four shapes: hashed static assets (/app.a1b2c3.js, where the hash is the version), cacheable API responses, dynamic but cacheable HTML such as post pages, and user-specific content that usually shouldn't touch the CDN at all. Treating them all the same is where teams get into trouble.

Strategy 1: Versioned URLs (the easy case)

Every static asset has a content hash in its filename. app.css becomes app.a1b2c3.css. When you change the source, the hash changes, the URL changes, and the CDN treats it as a new resource.

Cache-Control on these:

code
Cache-Control: public, max-age=31536000, immutable

One year, and immutable tells browsers not to revalidate while the copy is fresh. The hash gives you correctness; the long TTL gives you performance.

This is the gold standard, and most build tools (Webpack, Vite, esbuild) produce hashed names automatically. The HTML that references those assets needs a short TTL, because it changes on every deploy.

Strategy 2: Cache-Control with sensible TTLs

For content that can't be content-hashed (blog post pages, dynamic API endpoints):

code
Cache-Control: public, s-maxage=3600, stale-while-revalidate=86400

What this means:

  • s-maxage=3600: shared caches (the CDN) keep it fresh for 1 hour.
  • stale-while-revalidate=86400: for up to 24 hours after it goes stale, the cache may serve the stale copy while it fetches a fresh one in the background (RFC 5861).

This is the underrated piece: users always get a fast cached response while freshness updates happen behind the scenes. Cloudflare reports these responses as cf-cache-status: UPDATING.

Tune the TTL per content type:

  • High-update content (homepage with latest posts): 5 min s-maxage
  • Stable content (FAQ, terms page): 1 day s-maxage
  • Truly static within a deploy (compiled output): a year, hashed

Strategy 3: Explicit invalidation when needed

For content that needs to change immediately (a published post with a typo, a withdrawn product), use explicit invalidation. On CloudFront there are two mechanisms.

Invalidation paths. Submit the exact paths, or a wildcard:

Terminal
aws cloudfront create-invalidation \
    --distribution-id E2EXAMPLE123 \
    --paths "/blog/cdn-cache-invalidation-strategies" "/images/*"
aws cloudfront get-invalidation --distribution-id E2EXAMPLE123 --id I3EXAMPLE456

Billing is per path, not per file: the first 1,000 paths a month are free across the whole AWS account, and a wildcard like /* counts as one path no matter how many objects it clears. Beyond that you pay per path (see the CloudFront pricing page for the current rate). Use paths for one-offs.

Versioned origin paths. Instead of invalidating, change the URL. The post becomes /blog/cdn-cache-invalidation-strategies?v=2 (if query strings are in the cache key) or gets a content hash in the path. The CDN treats it as a new resource. Same caching, no invalidation needed, but every page linking to the old URL has to change too.

Strategy 4: Surrogate keys and cache tags

Tag responses with logical IDs, then invalidate by tag. Every product page gets a header like Surrogate-Key: product-123; when product 123 changes, you purge that one key and every cached response carrying it is invalidated, across however many URLs.

All three major CDNs support this as of October 2026:

CDNTag headerPurge callPlan / setupNotable limits
FastlySurrogate-Key (space-separated)Purge by key APIStandard caching1 KB per key, 16 KB header
CloudflareCache-Tag (comma-separated)Purge by tag APIAll plans, including Free16 KB header; Free plan 5 tag purges/min
Amazon CloudFrontAny header you name in CacheTagConfigcreate-invalidation --paths "#tag"Opt-in per distribution50 tags per object, 256 chars per tag

CloudFront's version is the newest of the three and changes an old workaround. Teams used to keep a mapping table of "content ID to cached URLs" and invalidate each path; now the origin returns a header such as x-amz-meta-cache-tag: product:123, category:tv and you invalidate #product:123. Each tag counts as one path against the same 1,000 free paths. One gotcha from AWS's own docs: if you rename the tag header in CacheTagConfig, objects cached under the old header name no longer match tag invalidations, so send both headers during the switch.

A worked example: a Next.js blog behind a CDN

For a typical content site we would use the TTLs in the cheat sheet below, tag every post page post:<id>, and purge that tag on each admin edit. Without the edit-time purge, readers could wait up to an hour to see a fix. One trap here: framework-level revalidation such as Next.js revalidatePath refreshes the framework's own cache, not a third-party CDN sitting in front of it. If you add CloudFront or Fastly on top of your host, you need the CDN purge call as well.

What fails in production

Short TTLs everywhere. "Just cache for 60 seconds so it's always fresh-ish." Sounds safe. Result: popular pages hit origin every minute per edge location, which negates most of the caching benefit. The cache only helps if the TTL is meaningfully longer than the gap between requests for that content.

Wildcard invalidation on every deploy. --paths "/*" after each deploy is one billable path, so cost isn't the issue; the issue is that it throws away every cached object, sends a thundering herd at your origin, and hides the fact that your assets aren't hashed. Hash your assets so deploys don't need invalidation.

Caching logged-in content. Short per-user TTLs risk serving one user's page to another if the key is wrong, and varying on the auth cookie creates one entry per session and drives the hit rate toward zero. Use Cache-Control: private and keep the CDN out of it.

Watch the hit ratio, not the purge log

Track cache hit ratio per path pattern and origin request rate. A hit-ratio drop or an origin spike after a deploy means cache busting you didn't expect, and a climbing daily invalidation count means purges are covering for unhashed assets. The general theory is in what is a CDN; the application-side equivalents are in our caching patterns guide.

Per-content-type guidance

A cheat sheet:

Content typeStrategy
Compiled JS/CSS with hashmax-age=31536000, immutable
Logo, faviconsmax-age=86400 (changes rarely; deploy bumps if needed)
Blog post HTMLs-maxage=3600, stale-while-revalidate=86400 + tag purge on edit
Sitemap, RSSs-maxage=21600
OG images / social-share imagess-maxage=604800
User-specific (logged-in pages)Cache-Control: private; don't cache at CDN
API: stable list endpointss-maxage=300
API: user-specificCache-Control: private
API: real-time dataCache-Control: no-cache

Questions people ask

How long does a CloudFront invalidation take?

It is not instant, and the CLI returns as soon as the request is accepted, not when edges have dropped the objects. Run aws cloudfront get-invalidation and wait for Completed before declaring the fix live. Because of that lag, invalidation is a poor fit for routine deploys: content-hash your assets so new versions get new URLs, and keep invalidation for urgent edits to HTML or API responses that can't be versioned.

What is the difference between purging and cache-busting?

Purging tells the CDN to drop objects it already holds, by URL, wildcard or tag. Cache-busting changes the URL itself, usually with a content hash or version parameter, so the CDN sees a brand-new object and the old one simply ages out. Busting is instant and free; purging is needed when the URL can't change, such as a page people have bookmarked.

Does Cloudflare purge by tag work on the free plan?

Yes, as of October 2026. Cloudflare's docs list purge by tag, hostname and prefix on all plans, including Free. Limits scale by plan: Free allows 5 of these purge requests per minute with up to 100 operations each, while Enterprise allows 50 per second. Tag your responses with the Cache-Tag header, comma-separated, kept under 16 KB in total.

The call we'd make

Content-hash everything you can, put stale-while-revalidate on everything you can't, and classify content into a handful of TTL tiers instead of picking one number. Use explicit purges only for user-visible edits, and make them tag-based now that every major CDN supports it. Never cache logged-in content at the CDN. Then watch hit ratio per path, because it catches misconfiguration faster than anything else. Cache invalidation isn't hard per content type; it's hard when you have many content types and one strategy.

Sources

Explore topics:Cloud
React
Share this post
Cite this article
Plain text

Kiril Urbonas (2026). CDN Cache Invalidation: Strategies That Don't Break in Production. DevOpsNess. Updated 2026-10-31. https://www.devopsness.com/blog/cdn-cache-invalidation-strategies

Markdown

[CDN Cache Invalidation: Strategies That Don't Break in Production](https://www.devopsness.com/blog/cdn-cache-invalidation-strategies) (Kiril Urbonas, DevOpsNess, updated 2026-10-31)

KU

Kiril Urbonas

Founder & Chief Editor

590 articles
View all articles by Kiril Urbonas

You might have missed

Evergreen posts worth revisiting.