Cache-Control cheat sheet

The Cache-Control values to send for each kind of file, what each directive means, pairs that get mixed up, how validation works, and commands to test it.

IntermediateReference3 min readUpdated October 30, 2026

In this guide
  1. What to send
  2. The directives
  3. Pairs people mix up
  4. Validation
  5. Vary
  6. Test it
  7. What Lighthouse checks
  8. Do not
  9. Check your own page
  10. Sources

What to send

Kind of fileSend
CSS, JavaScript, images and fonts with a version or hash in the nameCache-Control: max-age=31536000, immutable
Files with no version in the nameA short max-age, or no-cache
An HTML page that is the same for everyoneCache-Control: no-cache and an ETag
The same page, behind a CDN you can purgeCache-Control: max-age=0, s-maxage=3600
A page that differs per visitorCache-Control: private, no-cache
A page with data nobody else may seeCache-Control: no-store
API data that may be a minute oldCache-Control: max-age=60, stale-while-revalidate=300

The directives

DirectiveMeaning
max-age=NFresh for N seconds after the server made it. 31536000 is one year
s-maxage=NLike max-age, for shared caches only. Wins over max-age there, and browsers ignore it
no-cacheMay be stored, but must be checked with the server before each reuse
no-storeDo not store it anywhere
privateOnly the visitor's own browser may store it
publicAny cache may store it, even when the request had an Authorization header
must-revalidateOnce stale, never reuse it without checking
immutableWill not change while fresh, so do not check it
stale-while-revalidate=NMay be used for N seconds after it is stale, while a fresh copy loads in the background
stale-if-error=NMay be used for N seconds after it is stale if the server answers with an error
no-transformNobody on the way may change the content

Directive names are not case sensitive, but lower case is safest. If directives clash, the most restrictive one wins.

Pairs people mix up

TheseAre different because
no-cache and no-storeno-cache keeps a copy and checks it each time. no-store keeps nothing
private and "do not cache"private is about where: a browser may keep it, a shared cache may not
max-age and s-maxageThe first is for every cache. The second is for shared caches only
max-age=0, must-revalidate and no-cacheMDN says they mean the same
public and the defaultA response with max-age is already storable by shared caches. public matters for Authorization requests

Validation

HeaderDirectionUse
ETagResponseA label for this version of the file
Last-ModifiedResponseWhen the server thinks it last changed
If-None-MatchRequest"Do you still have the version with this label?"
If-Modified-SinceRequest"Has it changed since this date?"
304 Not ModifiedResponse"Keep using your copy." No body is sent

A normal reload sends max-age=0 with these validators, so you see many 304s. A force reload sends no-cache and downloads everything.

Vary

Vary: Accept-Encoding tells a cache that the answer depends on the compression the visitor accepts. See text compression. Avoid Vary: User-Agent: it has so many values that a cache rarely finds a match. To keep personal pages out of shared caches, use Cache-Control: private, not Vary: Cookie.

Test it

# the policy headers of a file
curl -s -o /dev/null -D - https://example.com/assets/app.3f9c1b.js | grep -i -E '^(cache-control|etag|last-modified|expires|age|vary)'

# does a check end in 304? (use the ETag from the first command)
curl -s -o /dev/null -D - -H 'If-None-Match: "the-etag-value"' https://example.com/assets/app.3f9c1b.js | head -n 1

In the Network panel, a file from the browser's cache shows (disk cache) or (memory cache) in the Size column. Untick Disable cache, and open the page by a link or in a new tab, not with Reload.

What Lighthouse checks

The Use efficient cache lifetimes insight lists fonts, images, media, scripts and stylesheets whose lifetime is shorter than 30 days, and estimates the bytes a returning visitor would download again. It skips files marked no-cache, no-store, must-revalidate or private.

Do not

Check your own page

Run the free audit and open the caching finding. Then run the first curl command on your main CSS file and your home page, and compare the answers with the first table.

Sources

Start here

Free website audit

Live Google data for any public page: mobile and desktop.

Mobile + desktop · Lighthouse lab test · Real-user Chrome data when Google has it

Rather have this done for you? Speed optimization · Book a call

Related guides

All guides →