You shipped a new build, but edges are still serving the old file until its TTL expires. Two tools fix this: an invalidation that actively removes objects from every edge, and a cache-busting strategy that changes the URL so the new content has a new cache key and the old copy simply ages out.
They are not equivalent. Invalidations cost money past a free allowance and take time to propagate; versioned URLs are free and instant. This page shows how to invalidate via the SDK and when to avoid it entirely.
CreateInvalidation takes an InvalidationBatch with a Paths list and a unique CallerReference. Each path is an absolute URL path, optionally ending in a * wildcard.
The call returns immediately with Status: "InProgress". The removal propagates to all edges over the next seconds to a few minutes. Note the same Quantity-must-match-Items rule from the rest of the CloudFront API applies to Paths.
For a deploy script you usually want to wait until the invalidation completes before declaring the release done. Track it with the invalidation_completed waiter or by polling GetInvalidation.
The /* wildcard invalidates the entire distribution and, importantly, counts as a single path for billing. Listing 300 individual files, by contrast, counts as 300 paths. This billing model shapes the whole strategy below.
CloudFront gives a monthly free allowance of invalidation paths (1,000 at the time of writing), then bills per path beyond that. The unit is the path, not the invalidation request:
["/index.html"] is one path.
["/a.js", "/b.js", "/c.js"] is three paths.
["/*"] is one path but clears everything.
So a naive deploy that invalidates every changed file individually can rack up thousands of billable paths on a large site, while a single /* costs one path. The trade-off is that /* also throws away cache for files that did not change, forcing edges to refill them from the origin and briefly raising origin load and latency.
Paths are absolute (they start with /) and URL-encoded. A trailing * is the only wildcard and matches any suffix: /css/* clears everything under /css/. Paths match against the object path only, not query strings - if your cache key includes a query string, invalidating /page clears every variant of /page regardless of query. There is a limit on wildcard invalidations in progress at once, so batch related paths into a single request rather than firing many.
The cheaper and faster strategy is to never invalidate at all. Give each build's assets a content-hashed filename - app.9f2a1c.js instead of app.js - so a new build produces a new URL with a new cache key. The old file ages out on its own TTL while the new one is fetched fresh on first request. Because the cache key changed, there is nothing to invalidate.
Pair this with long-lived Cache-Control on the hashed assets and a short TTL (or no-cache) on the small, unhashed HTML entry point that references them:
# Fingerprinted assets - cache forever, they never change under this nameCache-Control: public, max-age=31536000, immutable# HTML entry point - always revalidate so it points at the newest assetsCache-Control: no-cache
The origin sends these headers; CloudFront honors them within the behavior's MinTTL/MaxTTL. With this setup a deploy needs zero invalidations: only the tiny HTML is ever re-fetched, and it in turn points browsers and edges at the new fingerprinted files. Reserve CreateInvalidation for the rare case where you must purge already-served content immediately - a mistaken publish, a legal takedown, or an emergency rollback.
Reusing a CallerReference. If you reuse one with a different batch you get an error; if you reuse it with the same batch you get the previous invalidation back instead of a new one. Generate a fresh reference per call.
Invalidating file-by-file on every deploy. This is the classic cost leak. Use versioned URLs so most deploys need no invalidation at all.
Assuming invalidation is instant. It returns InProgress and propagates over seconds to minutes. Wait on completion if correctness depends on it.
Forgetting query strings. Invalidation matches the path only. /page clears all query-string variants of /page; you cannot invalidate a single query variant.
Quantity not matching Paths.Items. As everywhere in the CloudFront API, the count must equal the list length.
Relying on /* to fix a stale HTML loop. If your HTML is cached long, viewers keep loading old markup; fix the root cause with no-cache on HTML rather than invalidating repeatedly.
Versioned / fingerprinted URLs (described above) are the default for static assets - free, instant, and safe under rollback.
Short TTLs let objects expire on their own; good for feeds and dashboards that tolerate a minute of staleness without any invalidation.
Cache-key design (via a cache policy) can vary content by a query string or header so you never need to purge a shared URL - you just request a different one.
Origin-driven Cache-Control lets the origin dictate freshness per response, which is often enough that invalidations become the exception, not the routine.
Invalidate only when you must remove already-cached content right now; for routine deploys, change the URL instead.
Call CreateInvalidation with the object's path in Paths.Items and a unique CallerReference. It removes matching objects from every edge over the next few minutes.
How are invalidations billed?
By path. There is a monthly free allowance (1,000 paths), after which each path beyond it is billed. A request that lists ten files counts as ten paths.
Does /* cost more than one file?
No. /* counts as a single path even though it clears the whole distribution. It is cheaper than listing many individual files, but it also evicts unchanged objects.
How long does an invalidation take?
It returns InProgress immediately and completes in seconds to a few minutes as it propagates to all edges. Track it with GetInvalidation or the invalidation-completed waiter.
What is a versioned URL and why is it better?
A filename that includes a content hash, like app.9f2a1c.js. A new build changes the name, hence the cache key, so the new content is fetched fresh and the old ages out - no invalidation, no cost.
Can I invalidate a single query-string variant?
No. Invalidation matches the object path only. Invalidating /page clears every query-string variant of /page.
What Cache-Control should I use for hashed assets?
public, max-age=31536000, immutable - cache them effectively forever, since a new build gets a new name. Keep the unhashed HTML entry point on no-cache.
Why did reusing a CallerReference not create a new invalidation?
A CallerReference is a dedupe key. Reusing it with the same batch returns the earlier invalidation; reusing it with a different batch errors. Generate a fresh one each call.
Do wildcards work in invalidation paths?
Yes, a trailing * matches any suffix, so /css/* clears everything under /css/. It is the only supported wildcard and applies to the end of the path.
Should my deploy pipeline invalidate on every release?
Usually no. With fingerprinted asset URLs and no-cache HTML, most deploys need zero invalidations. Reserve invalidation for emergencies like a bad publish or takedown.