What are http cache headers?
HTTP cache headers are part of the HTTP protocol and control how web browsers and intermediary caches (like CDNs or proxy servers) store and reuse responses. They help improve performance, reduce bandwidth usage, and minimize server load by avoiding unnecessary requests for resources that haven’t changed.
Here’s a breakdown of the key HTTP cache headers:
This header tells the browser or intermediary how, and for how long, the response can be cached.
Examples:
Cache-Control: no-cache
– the resource
must be revalidated
with the server before using it.
Cache-Control: no-store
– the response
must not be stored
in any cache.
Cache-Control: public, max-age=3600
– the response can be cached by
any cache
and is
fresh for 3600 seconds.
Cache-Control: private, max-age=600
– only the
client browser
can cache the resource for 600 seconds.
This is an older header (mostly replaced by
Cache-Control
) that specifies a
date/time after which
the response is considered stale.
Example:
Expires: Wed, 21 Oct 2025 07:28:00 GMT
If both
Cache-Control
and
Expires
are present,
Cache-Control takes precedence.
Used for
conditional requests. It's a fingerprint (hash) of the response. When the
browser makes a subsequent request, it includes the
ETag
via the
If-None-Match
header. The server compares it and returns:
304 Not Modified
if the content hasn’t changed.
200 OK
with the new content if it has changed.
Example:
ETag: "abc123"
Specifies the
last time the resource was changed. Works similarly to
ETag
.
Browser sends
If-Modified-Since
.
Server responds with
304 Not Modified
if unchanged.
Example:
Last-Modified: Tue, 10 Jun 2025 10:00:00
GMT
Header | Purpose |
---|---|
Cache-Control | Main directive for how caching works |
Expires | Sets expiration date/time (older standard) |
ETag | Validates if resource has changed |
Last-Modified | Another way to validate if resource changed |
Would you like a real-world example showing how these are used in an HTTP response?
Great question — the validity of HTTP cache headers is not limited to just the browser session. It depends on how the resource is cached and what directives are used. Here's how it breaks down:
Cached only in RAM.
Lost when the browser is closed.
Used for resources with
no persistent caching policy
or certain
Cache-Control
headers like
no-store
.
Stored on disk, so it persists across browser sessions.
Resources with headers like:
httpCache-Control: public, max-age=86400
can be stored in disk cache and reused
even after the browser is closed and
reopened, as long as they’re still "fresh" (within
max-age
).
Header / Directive | Persists After Browser Close? | Notes |
---|---|---|
Cache-Control: max-age=...
|
✅ Yes | If stored to disk. |
Cache-Control: no-store
|
❌ No | Not cached at all. |
Cache-Control: no-cache
|
🔄 Needs revalidation | Cache exists but must revalidate. |
ETag ,
Last-Modified
|
✅ Yes | Used for revalidation (can persist). |
Expires
|
✅ Yes |
Acts like
max-age .
|
Persistent caching (on disk) means resources survive browser restarts, as long as the cache headers allow it.
In-memory caching
(like some
no-store
or
private
resources) vanishes once the browser is closed.
If you want to ensure that resources don’t persist after closing the browser, you should use:
httpCache-Control: no-store
Or if you want them to persist (e.g., for performance), use:
httpCache-Control: public, max-age=86400
Would you like to see how a specific browser (like Chrome) handles this under the hood?
The
Cache-Control: no-cache
directive is
often misunderstood, so here's a clear explanation:
no-cache
actually
mean?
Contrary to how it sounds,
no-cache
does
not prevent
caching.
Instead, it means:
✅ You can store the response in cache,
❌ But you must revalidate it with the server before using it again.
When a browser caches a resource with
no-cache
,
it still stores it (on disk or in memory), but every
time that resource is requested again, the browser
sends a
conditional request
to the server with headers like:
httpIf-None-Match: "abc123" ← uses ETag If-Modified-Since: Tue, 10 Jun 2025 10:00:00 GMT
If the resource hasn’t changed, the server replies:
http304 Not Modified
And the browser can use the cached copy.
no-cache
?
no-cache
is useful when:
You want to avoid stale content but still save bandwidth.
You expect content might change often, but not every time.
You want controlled freshness with validation.
Directive | Stored? | Revalidated? | Used without check? |
---|---|---|---|
no-cache
|
✅ Yes | ✅ Yes | ❌ No |
no-store
|
❌ No | ❌ No | ❌ No |
max-age=600
|
✅ Yes | ❌ No (until expiry) | ✅ Yes |
For a logged-in user dashboard, you might use:
httpCache-Control: private, no-cache, must-revalidate
private
→ only store in user's browser.
no-cache
→ must revalidate.
must-revalidate
→ ensures the revalidation always happens even
when offline.
Let me know if you want an example response with all the headers combined or how to configure this in a specific framework like Express.js or Nginx.
Correct —
no-cache
does
not allow use
of the cached resource when offline by
default.
Here’s what happens:
Cache-Control: no-cache
is set:
The browser wants to revalidate the cached response with the server.
But since it can’t reach the server, the validation fails.
Result: The resource is not used, and you get an error (like a failed network request).
Because
no-cache
explicitly tells the browser:
"You must check with the server before using this."
So if the browser can't check (because you're offline), it refuses to use even a previously cached copy.
must-revalidate
This makes the behavior
stricter. With
must-revalidate
, even if the cache is expired and you're
online, the browser
won’t fall back
to using stale content.
Without
must-revalidate
, some browsers
might use
stale cache under certain edge cases — like being
temporarily offline.
Use
Cache-Control
with a longer
max-age
, or design a
Service Worker
(for full offline-first behavior).
Example:
httpCache-Control: public, max-age=86400
This tells the browser: "You can use this resource for up to 1 day without checking back."
Would you like a code snippet or example of configuring this for offline use (e.g., in a PWA or Service Worker)?
Great question — the
public
and
private
directives in
Cache-Control
are about
who is allowed to cache the response.
public
Meaning:
"This response can be cached by anyone — including shared caches like CDNs, proxy servers, and browsers."
Use case:
For
static assets
or
non-user-specific content
(e.g., CSS, JS, images, public pages).
Example:
httpCache-Control: public, max-age=86400
→ Cacheable by browser and intermediary caches (like Cloudflare, Varnish, etc.) for 1 day.
private
Meaning:
"This response is intended for a single user, and must only be cached by their browser — not by shared caches."
Use case:
For
user-specific data, such as account pages, dashboards, or personalized
content.
Example:
httpCache-Control: private, max-age=600
→ Cached by the browser only, and only for 10 minutes. Intermediary caches must not store it.
public
or
private
?
If neither is specified, behavior varies:
Some caches may treat it as
cacheable, others may not — depends on headers like
Authorization
and the HTTP method used.
It's safer to explicitly set one of them for predictable behavior.
Directive | Cacheable by Browser | Cacheable by Shared Caches (CDNs, Proxies) | Typical Use Case |
---|---|---|---|
public
|
✅ Yes | ✅ Yes | CSS, JS, images, APIs for all users |
private
|
✅ Yes | ❌ No | User profiles, account info |
(None) | 🤷♂️ Maybe | 🤷♀️ Maybe | Risky — depends on other headers |
Let me know if you'd like to see how this works in browser DevTools or how to set this up in a web server like Nginx or Express.