Updated 9 hours ago
Every time your browser fetches a resource, it's asking the same question: do I already have this?
If yes, it can skip the network entirely—no latency, no bandwidth, instant response. If no, it waits. The difference between a 100-millisecond page load and a 3-second one often comes down to how well you've answered that question in advance.
Cache-Control is how you answer it.
The Core Idea
Caching is a bet on time. When you set max-age=3600, you're saying: "This response will remain true for the next hour. Trust it." When you set max-age=31536000, you're saying: "This will be true for a year."
Every max-age value is a bet: how long will this truth remain true?
Get it right, and your application feels instant. Get it wrong, and users see stale data—or worse, you waste bandwidth re-fetching content that hasn't changed.
The Syntax
Cache-Control appears in HTTP responses (and sometimes requests). Multiple directives combine with commas:
Each directive answers a different question about how to cache.
Who Can Cache? (public vs. private)
public: Anyone can cache this—browsers, CDNs, corporate proxies, your ISP.
Use for content that's identical for all users: images, stylesheets, JavaScript, fonts.
private: Only the end user's browser can cache this. Shared caches must not store it.
Use for user-specific content: dashboards, account pages, personalized API responses.
The distinction matters for security. A CDN caching your user's personal data and serving it to someone else is a breach. private prevents that.
How Long? (max-age and s-maxage)
max-age sets freshness lifetime in seconds:
Common values:
60— one minute (rapidly changing content)3600— one hour (moderately dynamic)86400— one day (daily updates)604800— one week31536000— one year (versioned static assets)
s-maxage overrides max-age for shared caches only:
Browsers cache for one minute; CDNs cache for an hour. Useful when you want CDN efficiency but browser freshness.
The Confusingly Named Directives
no-cache doesn't mean "don't cache." It means "cache it, but ask me before using it."
The cache stores the response but validates with the server before serving it. If unchanged, the server says "304 Not Modified" and the cache serves its copy. If changed, fresh content arrives.
This gives you freshness guarantees while still benefiting from caching when nothing has changed.
no-store actually means don't cache:
Nothing gets stored. Every request hits the origin. Use for sensitive data: banking transactions, medical records, passwords.
Revalidation Directives
must-revalidate: Once stale, always revalidate. Don't serve stale content even if the server is unreachable.
Without this, some caches might serve stale content when they can't reach the origin. With it, they return an error instead.
immutable: This content will never change. Don't bother revalidating, ever.
Perfect for versioned assets like app.v2.4.1.js. The filename changes when content changes, so the URL is effectively a content address. Browsers can skip revalidation entirely, even on refresh.
How Validation Works
When cached content goes stale, the browser asks: "Is this still good?"
Two mechanisms make this efficient:
ETags are content fingerprints:
Last-Modified uses timestamps:
ETags are more precise—they can be based on content hashes. Last-Modified has one-second granularity and can't detect sub-second changes.
Practical Strategies
Versioned Static Assets
Filename: styles.a8f3b2.css
Cache forever. When content changes, the filename changes, and browsers fetch the new URL. This is the gold standard for static assets.
HTML Documents
HTML references other resources. If it's cached too long, users might see old HTML pointing to old assets. Require validation to ensure freshness.
API Responses
For data that can tolerate some staleness:
Five-minute caching reduces server load significantly.
For real-time data:
For sensitive data:
User-Uploaded Images
One-week caching works well for content that changes rarely.
Common Mistakes
Caching error responses: A 500 error cached for an hour means an hour of broken experience. Errors should use no-store.
Using public for user-specific content: This can leak private data through shared caches. Always use private for personalized responses.
Thinking no-cache prevents caching: It doesn't. It requires validation. For no caching, use no-store.
No Cache-Control at all: Without explicit headers, caching behavior varies unpredictably across browsers and proxies. Always be explicit.
Debugging
In browser DevTools (Network tab), the Size column reveals cache status:
- A number like "45.2 KB": fetched from network
- "(disk cache)": served from disk
- "(memory cache)": served from memory
Click any request to inspect its Cache-Control headers.
The "Disable cache" checkbox bypasses caching during development—useful for testing changes without clearing your cache.
Frequently Asked Questions About Cache-Control Headers
Was this page helpful?