Cache Control Headers
title: "Cache-Control Directives — max-age, no-cache, no-store, and More" description: "Cache-Control directives (max-age, no-cache, no-store, private, public, must-revalidate, s-maxage) govern how browsers and proxies cache HTTP responses." date: 2026-06-28 lastmod: 2026-06-28 weight: 13 tags: [apis, caching] }
Cache-Control directives provide fine-grained control over HTTP caching behavior, including freshness duration, storage restrictions, and revalidation requirements.
What You'll Learn
- Common Cache-Control directives
- Directive combinations and precedence
- Shared vs private cache directives
Why It Matters
Cache-Control is the primary mechanism for HTTP caching. Understanding each directive prevents caching sensitive data and ensures correct cache behavior.
Directive Comparison
| Directive | Meaning | Example |
|---|---|---|
public |
Any cache can store | Cache-Control: public, max-age=3600 |
private |
Browser-only cache | Cache-Control: private, max-age=3600 |
no-cache |
Must revalidate with origin | Cache-Control: no-cache |
no-store |
Never cache | Cache-Control: no-store |
max-age=N |
Fresh for N seconds | Cache-Control: max-age=300 |
s-maxage=N |
Proxy/CDN max-age | Cache-Control: public, s-maxage=3600 |
must-revalidate |
Must check if stale | Cache-Control: must-revalidate |
immutable |
Never changes (static assets) | Cache-Control: immutable, max-age=31536000 |
Code Examples
# Different Cache-Control strategies
@app.route('/api/products')
def list_products():
# Public, cache for 5 minutes
resp = jsonify(products)
resp.headers['Cache-Control'] = 'public, max-age=300'
return resp
@app.route('/api/users/me')
def current_user():
# Private, browser only
resp = jsonify({'id': current_user.id, 'email': current_user.email})
resp.headers['Cache-Control'] = 'private, max-age=60'
return resp
@app.route('/api/balance')
def get_balance():
# Never cache
resp = jsonify({'balance': get_user_balance()})
resp.headers['Cache-Control'] = 'no-store'
return resp
@app.route('/api/status')
def system_status():
# Must revalidate every time
resp = jsonify({'status': 'healthy'})
resp.headers['Cache-Control'] = 'no-cache'
return resp
@app.route('/api/config')
def site_config():
# CDN caches for 1 hour, browser for 5 minutes
resp = jsonify(config)
resp.headers['Cache-Control'] = 'public, max-age=300, s-maxage=3600'
return resp
Common Mistakes
1. Using no-cache When You Mean no-store
no-cache still stores the response; it just requires revalidation.
2. Missing s-maxage for CDNs
Without s-maxage, CDNs use max-age, which may be too short.
3. Conflicting Cache-Control Headers
Don't set both public and private — they cancel out.
4. Forgetting Immutable for Static Assets
Static files with hashed filenames should use immutable.
5. Using no-store on Public Endpoints
Public endpoints should use appropriate caching, not no-store.
Practice Questions
- What is the difference between no-cache and no-store?
- What does s-maxage do?
- When would you use must-revalidate?
- What does the immutable directive mean?
- Can you combine multiple Cache-Control directives?
Answers:
- no-cache stores but requires revalidation; no-store prevents all storage.
- It sets max-age specifically for shared caches (proxies, CDNs).
- When cached data must always be validated if stale, never served stale.
- The response never changes, so browsers don't revalidate.
- Yes, comma-separated:
public, max-age=3600, must-revalidate.
Challenge: Analyze your API endpoints and assign the correct Cache-Control strategy for each: public, private, no-cache, or no-store.
FAQ
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro