Http Caching
title: "HTTP Caching — Cache-Control, ETag, and Expires Headers" description: "HTTP caching uses Cache-Control, ETag, and Expires headers to instruct browsers and proxies how long to cache responses under the HTTP/1.1 specification." date: 2026-06-28 lastmod: 2026-06-28 weight: 12 tags: [apis, caching] }
HTTP caching relies on Cache-Control, ETag, and Expires headers to define caching policies, enabling browsers and intermediate proxies to serve cached responses.
What You'll Learn
- The HTTP caching model
- Key caching response headers
- Freshness vs validation
Why It Matters
Proper HTTP caching is the foundation of web performance. Correct headers reduce bandwidth, improve latency, and lower origin server load.
HTTP Caching Cycle
flowchart TD
subgraph Fresh
C[Client Request] -->|Has fresh cache| C2[Serve from Cache]
end
subgraph Stale
C -->|Cache expired| V[Validate with ETag]
V -->|304 Not Modified| V2[Use Stored Response]
V -->|200 + New Body| O[Origin Server]
end
Code Examples
# Setting cache headers in Flask
@app.route('/api/users')
def get_users():
response = jsonify(users)
response.headers['Cache-Control'] = 'public, max-age=3600'
response.headers['ETag'] = generate_etag(users)
response.headers['Expires'] = (datetime.utcnow() + timedelta(hours=1)).strftime(
'%a, %d %b %Y %H:%M:%S GMT'
)
return response
# Conditional request handling
@app.route('/api/users')
def get_users_conditional():
users = db.get_users()
current_etag = hashlib.md5(str(users).encode()).hexdigest()
if request.headers.get('If-None-Match') == current_etag:
return '', 304
response = jsonify(users)
response.headers['ETag'] = current_etag
response.headers['Cache-Control'] = 'public, max-age=3600'
return response
// Node.js Express caching
app.get('/api/products', (req, res) => {
const products = db.getProducts();
const etag = crypto.createHash('md5').update(JSON.stringify(products)).digest('hex');
if (req.headers['if-none-match'] === etag) {
return res.status(304).end();
}
res.set({
'Cache-Control': 'public, max-age=3600',
'ETag': etag,
'Expires': new Date(Date.now() + 3600000).toUTCString()
});
res.json(products);
});
Common Mistakes
1. No Cache-Control Header
Browsers may cache arbitrarily without explicit directives.
2. Overly Long max-age
Stale data persists for too long. Set appropriate TTLs.
3. Missing ETag
Without ETag, clients must re-download unchanged resources.
4. Using Last-Modified Instead of ETag
Last-Modified has second granularity; ETag supports content hashing.
5. Ignoring Vary Header
Different clients may get wrong cached responses without Vary.
Practice Questions
- What does Cache-Control: public mean?
- How does ETag improve caching?
- What is the difference between Expires and max-age?
- What is a 304 response?
- Why is Vary header important?
Answers:
- Any cache (browser, proxy, CDN) may store the response.
- ETag enables conditional requests, avoiding re-download of unchanged content.
- Expires is absolute (HTTP/1.0); max-age is relative (HTTP/1.1, preferred).
- Not Modified — the client's cached version is still valid.
- It tells caches to vary the cached response by specified headers (e.g., Accept-Encoding).
Challenge: Implement ETag-based conditional responses for a JSON API endpoint. Measure bandwidth savings with and without ETag.
FAQ
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro