Skip to content

CloudFront cache invalidation: doing it fast, cheap, and rarely

How CloudFront invalidation works, what the 1,000 free paths per month actually mean, and the versioning strategy that makes invalidation something you almost never need.

To clear CloudFront’s cache right now: aws cloudfront create-invalidation --distribution-id E1A2B3C4D5E6F7 --paths "/*". It usually completes within a few minutes, and the first 1,000 invalidation paths per month per account are free — a wildcard like /* counts as one path no matter how many files it matches. That is the emergency answer. The operator answer is that a team invalidating regularly has a caching design problem: with versioned asset names and correct Cache-Control headers, invalidation becomes something you do a few times a year, not per deploy.

What’s happening

CloudFront caches objects at edge locations based on the cache key (path plus whatever headers, query strings, and cookies the cache policy includes) for as long as the TTL allows. An invalidation tells every edge location to mark matching objects stale, so the next request goes back to the origin. It does not push new content anywhere — it removes the guarantee that the old copy will be served.

The mechanics that matter in practice:

  • Paths are literal or trailing-wildcard. /index.html invalidates one object; /images/* invalidates everything under the prefix. The * must be the last character — /img/*.png is not a valid pattern.
  • The path is the request path, not the origin path. If a behavior rewrites or the origin has a path prefix, invalidate what the viewer requests.
  • Query strings and encoding count when the cache policy includes them: an invalidation of /page does not necessarily cover /page?v=2 as a distinct cached variant — /page* does.
  • Billing is per path submitted, not per file matched. After the 1,000 free paths in a month, each additional path carries a small per-path charge. Scripts that loop create-invalidation per file are how teams turn a free operation into a line item; one wildcard would have cost nothing.
  • It is eventually consistent. Most invalidations finish in a minute or two; heavily loaded distributions can take longer. Status is visible until it flips to Completed.

What to check first

  • Do you actually need an invalidation? If the stale object is an asset referenced by HTML, changing the filename (hash-versioning) fixes it without touching CloudFront.
  • Is the object even cached? Check the x-cache response header — Miss from cloudfront means the origin is serving the stale content itself.
  • What TTL is in play? A Cache-Control: max-age=31536000 on HTML coming from the origin means every deploy will need an invalidation until the header is fixed.
  • Is the cache key what you think? A cached variant per query string or header means your invalidation path may not match what is stored.

How to verify

Confirm what CloudFront is serving and why:

# Is the response cached, and how old is it?
curl -sI https://www.example.com/app.js | grep -iE 'x-cache|age|cache-control|etag'

# Submit the invalidation
aws cloudfront create-invalidation \
  --distribution-id E1A2B3C4D5E6F7 \
  --paths "/index.html" "/assets/*"

# Watch it complete
aws cloudfront get-invalidation \
  --distribution-id E1A2B3C4D5E6F7 --id I2J3K4L5M6N7O8

# Count this month's invalidation history
aws cloudfront list-invalidations --distribution-id E1A2B3C4D5E6F7

In the console the same lives at CloudFront > Distributions > E1A2B3C4D5E6F7 > Invalidations > Create invalidation. After it completes, re-run the curl and expect x-cache: Miss from cloudfront on the first request, then Hit with a fresh age after.

The runbook

  1. Version immutable assets instead of invalidating them. Build pipelines should emit app.4f9a2c.js, referenced from HTML, with Cache-Control: public, max-age=31536000, immutable. A new deploy produces new names; old ones can stay cached forever without harming anyone.
  2. Keep HTML and API entry points on short TTLs. Cache-Control: max-age=60 (or no-cache with ETag revalidation) on index.html means deploys become visible within a minute with zero invalidations.
  3. When you must invalidate, batch and wildcard. One request with /index.html, /, and /assets/* beats thirty per-file requests — same effect, three paths against the monthly 1,000 instead of thirty.
  4. Wire the rare invalidation into the deploy, not into humans. A pipeline step that invalidates the two or three HTML paths after an S3 sync is fine; a wiki page telling developers to invalidate /* manually is how the monthly count explodes.
  5. Use /* for emergencies without guilt. Leaked content, a bad deploy serving broken JS to the world — one wildcard path, done. The cost argument against /* is backwards; it is the cheapest invalidation per object matched.
  6. For instant selective refresh at scale, reconsider the design. If the product requires invalidating thousands of distinct paths per day, what you want is shorter TTLs, origin-controlled Cache-Control, or stale-while-revalidate — not an invalidation firehose.

When this is the wrong move

Invalidation does not fix an origin that is serving old content — if x-cache says Miss and the object is still stale, the problem is behind CloudFront (an S3 sync that did not run, a second origin, a proxy cache on the origin itself). It also does not fix browser caching: a user whose browser holds app.js with a year-long max-age will not re-request it no matter what CloudFront does, which is exactly why versioned filenames beat invalidation. And if you find yourself invalidating to make authentication or personalization changes visible, those responses should not have been cached in the first place — fix the cache policy on that behavior.

Common mistakes

  • Invalidating file-by-file from a deploy script, burning hundreds of billable paths where one wildcard was free.
  • Invalidating the origin path (/build/index.html) instead of the viewer path (/index.html) and concluding invalidation “does not work.”
  • Forgetting cached variants: the cache policy includes a header or query string, and the stale variant survives a path-only invalidation.
  • Treating invalidation as the fix for a missing Cache-Control strategy, so every single deploy depends on a manual step.

Keeping it under control

  • Hash-version every static asset in the build; reserve invalidation for HTML entry points.
  • Set explicit Cache-Control at the origin for every content class — never rely on defaults you have not read.
  • Track invalidation counts per month; a rising trend is a design smell, not a growing bill to accept.
  • Give deploy pipelines a scoped IAM permission for cloudfront:CreateInvalidation on the specific distribution, so the emergency path works at 2 a.m. without an admin.

The behavior-level details — TTL precedence, cache keys, invalidation limits — are laid out in the CloudFront documentation, and the certificate side of a distribution has its own trap covered in custom domains and ACM on CloudFront. If nobody on your team truly owns the AWS environment end to end, that is the root of most of these problems — Stack Harbor runs AWS accounts daily under AWS Operations, and you can book an AWS review.