How to check cache-control header
Print the response headers and keep one line: curl -s -D - -o /dev/null https://example.com/ | grep -i cache-control. Read the directives from left to right. max-age sets the lifetime in seconds for the browser, s-maxage replaces that number for shared caches only, and no-store forbids storage in either of them.
Why check this
This check belongs on staging sign-off and after every change to a cache layer, a CDN rule or a build pipeline. The failure it prevents is a release users cannot see: an unversioned app.js shipped with max-age=31536000 stays in the browser for a year, and the bug reports all say the fix appears after a hard refresh. The same directives decide whether a response holding one customer's data may sit on a shared edge, which is why the command also runs on authenticated routes.
Prerequisites
- curl 7.0 or later. Any build reads headers, no HTTP/2 support needed. See the curl manual.
- The full URL of the resource under test, query string included. Caches key on the whole URL, so
/app.jsand/app.js?v=3are separate entries. - RFC 9111 section 5.2 for the directive list, and the MDN Cache-Control reference for browser behaviour.
Steps
- Step 1.
Read the header from the resource, with
expiresandagealongside it so the whole freshness picture arrives in one request.curl -s -D - -o /dev/null 'https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control' | grep -i -E '^cache-control|^expires|^age'expires: Fri, 11 Sep 2026 17:38:07 GMT cache-control: public, max-age=3600 age: 1126The
expiresdate is already in the past while the response is still fresh.cache-controloutranksexpires, so the lifetime is 3600 seconds and 1126 of them are spent. - Step 2.
Look for a second number aimed at shared caches.
s-maxageapplies to the CDN and is ignored by the browser.curl -s -D - -o /dev/null 'https://api.github.com/repos/curl/curl' | grep -i -E '^cache-control|^vary'cache-control: public, max-age=60, s-maxage=60 vary: Accept,Accept-Encoding, Accept, X-Requested-WithRead
varyin the same pass. It names the request headers that split one URL into several stored variants. - Step 3.
Check a resource meant to be revalidated before every use.
max-age=0next tomust-revalidateis the shape to expect.curl -s -D - -o /dev/null 'https://www.cloudflare.com/favicon.ico' | grep -i '^cache-control'cache-control: public, max-age=0, must-revalidate - Step 4.
Count the header instead of printing it when a yes or no answer is enough. Zero means the server sends no directive at all.
curl -s -D - -o /dev/null 'https://example.com/' | grep -ci '^cache-control'0An absent header does not mean an absent cache. That same response carries
last-modified, and a cache may derive a lifetime from it without asking anyone.
How to read the result
| What you see | What it means | What to do |
| --- | --- | --- |
| max-age=31536000, immutable | Stored for a year, never revalidated | Correct only on a filename carrying a content hash. Wrong on /app.js. |
| no-store | Never written to disk or memory, anywhere | Correct on anything holding a session, a token or personal data. |
| no-cache | Stored, but revalidated before every use | The response also needs etag or last-modified, or every use is a full download. |
| private, max-age=600 | The browser may keep it, the CDN may not | The right pair for a per-user page served through a shared edge. |
| No header at all | The cache picks a lifetime by heuristic | Set one. A number guessed from last-modified is not a contract you control. |
Common mistakes
Thresholds
What to check next
- How to check ETag header: the validator that turns an expired entry into a 304 instead of a full download.
- How to check last-modified header: the date-based validator, and how to prove a real 304 comes back.
- How to check stale-while-revalidate: the extension that lets a cache answer after
max-ageruns out. - How to check if CDN cache is hit or miss: what the edge actually did with the directives you read here.
- How to check HTTP response headers with curl: the header-printing flags used above, explained in full.
FAQ
What is the difference between no-cache and must-revalidate?
no-cache revalidates before every use, fresh or not. must-revalidate allows normal use inside the freshness window and forbids serving the entry once that window closes. One removes the window, the other removes the grace period after it.
What is the difference between expires and cache-control?
expires is one absolute date. cache-control carries directives plus a relative lifetime, and it wins wherever both appear. Step 1 shows a response with an expires date already past that is still fresh, because max-age=3600 decides.
How to check cache control header in chrome?
Open DevTools, Network tab, click the request, then the Headers panel and its Response Headers section. The Size column reads "(disk cache)" or "(memory cache)" when the response never left the machine.
How to check cache headers with curl?
The four commands above are the check. -D - writes headers to stdout, -o /dev/null discards the body, and grep -i keeps the line you want. No curl flag stores or reuses a response, so curl reads the contract and never exercises it.
Verified
Verified by Maks Vernycurl 8.21.0
Each output block is what the command above it printed on that date, on the host named in the step. Figures read from a live site move between runs. Compare the shape of the answer rather than the digits, and see the methodology for how a page is re-verified.
Related on this site
basic4 minpublished updated Maks Verny