HTTP Caching Guide: Cache-Control, ETag, Expires
Why HTTP Caching Matters
HTTP caching is one of the most effective ways to speed up website loading. When a browser stores a local copy of a resource, subsequent requests are served instantly without contacting the server. This reduces server load, saves bandwidth, and dramatically improves user experience.
According to Google, reducing load time by 100ms increases conversions by 1%. Properly configured caching can reduce repeat visit load times by 80-90%.
Core Caching Headers
Cache-Control
The Cache-Control header is the primary caching mechanism in HTTP/1.1 and beyond. It supersedes the deprecated Pragma header and takes priority over Expires.
Key directives:
public— resource can be cached by any intermediary (CDN, proxy)private— cache only in the user's browser (for personalized content)max-age=N— cache lifetime in secondss-maxage=N— lifetime for shared caches (CDN), overridesmax-ageno-cache— may cache, but must revalidate with server before useno-store— completely prohibit caching (for sensitive data)must-revalidate— must check with server after TTL expiresimmutable— resource will never change (for hashed filenames)
Example for static assets with content hash:
Cache-Control: public, max-age=31536000, immutable
Example for HTML pages:
Cache-Control: no-cache
Example for API документацию with private data:
Cache-Control: private, no-store
ETag (Entity Tag)
An ETag is a unique identifier for a specific version of a resource. The server generates a hash of the content and sends it in the response header:
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
On subsequent requests, the browser sends If-None-Match with the stored ETag. If the resource hasn't changed, the server responds with 304 Not Modified without a body, saving bandwidth.
Two types of ETags exist:
- Strong ETag — byte-for-byte identical:
"abc123" - Weak ETag — semantically equivalent:
W/"abc123"
Last-Modified and If-Modified-Since
The Last-Modified header contains the date when the resource was last changed. It's a simpler validation mechanism than ETag but less precise — accuracy is limited to one second.
Last-Modified: Wed, 21 Oct 2025 07:28:00 GMT
On subsequent requests, the browser sends If-Modified-Since. ETag takes priority over Last-Modified when both are present.
Expires
A legacy HTTP/1.0 header that sets an absolute expiration date:
Expires: Thu, 01 Dec 2026 16:00:00 GMT
Problems with Expires: depends on client-server clock synchronization, less flexible than Cache-Control. Use Cache-Control: max-age instead.
Caching Strategies by Resource Type
HTML Documents
HTML pages typically reference CSS and JS files, so they should always check for freshness:
Cache-Control: no-cache ETag: "page-v42"
This allows the browser to cache the page but check for updates on every visit.
CSS, JavaScript, Fonts
Static resources with a hash in the filename (e.g., app.a1b2c3.js) can be cached indefinitely:
Cache-Control: public, max-age=31536000, immutable
Without a filename hash, use a shorter TTL:
Cache-Control: public, max-age=86400
Images
Images rarely change. For optimized images with unique URLs:
Cache-Control: public, max-age=2592000
That's 30 days — a good balance between caching and the ability to update.
API Responses
For APIs with frequently changing data:
Cache-Control: private, no-cache, max-age=0
For rarely updated public APIs:
Cache-Control: public, max-age=300, s-maxage=600
Common Mistakes
- no-cache does NOT mean don't cache. The
no-cachedirective allows caching but requires revalidation. For a complete ban, useno-store. - Same TTL for all resources. HTML and static assets need different strategies.
- No versioning. Without a hash or version in the URL, users will see stale CSS/JS after updates.
- Ignoring CDN. Without
s-maxage, CDN will usemax-age, which may not be desired. - Caching personal data as public. Responses with cookies or personalization should be
private.
How to Verify Your Caching Setup
Check your website's caching headers using the Enterno.io HTTP Checker. The tool displays all response headers including Cache-Control, ETag, Expires, and Last-Modified, helping you verify everything is configured correctly.
In Chrome DevTools, the Network tab's Size column shows (disk cache) or (memory cache) for cached resources, and a 304 status code for successfully revalidated ones.
Recommended nginx Configuration
# HTML — always revalidate
location ~* \.html$ {
add_header Cache-Control "no-cache";
}
# Hashed static assets — cache forever
location ~* \.(js|css)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
}
# Images — cache for 30 days
location ~* \.(jpg|jpeg|png|gif|webp|svg|ico)$ {
add_header Cache-Control "public, max-age=2592000";
}
# Fonts — cache for 1 year
location ~* \.(woff2?|ttf|eot)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
add_header Access-Control-Allow-Origin "*";
}
Summary
Proper HTTP caching is a balance between speed and content freshness. Use Cache-Control as your primary tool, ETag for validation, and file versioning for safe long-term caching. Regularly check your resource headers to ensure everything is configured correctly.
Check your website right now
Check now →