Caching Headers Explained
Understand HTTP caching headers, browser and intermediary caches, Cache-Control directives, validators and practical caching strategies.
HTTP caching headers tell browsers, proxies and other caches how responses should be stored and reused. They can significantly reduce network traffic, improve page loading performance and decrease the amount of work required from application servers.
Caching behavior is controlled primarily through response headers such as Cache-Control, Expires, ETag and Last-Modified. Understanding these headers helps developers decide which resources can be cached, how long they should remain fresh and when a cached response needs to be validated with the server.
What Are HTTP Caching Headers?
HTTP caching headers are response headers that communicate caching instructions between a server, a client and intermediary caches. They define whether a response can be stored, how long it can remain fresh and how a cache should validate an existing response.
| Header | Primary Purpose |
|---|---|
| Cache-Control | Defines caching rules and freshness |
| Expires | Provides an expiration date |
| ETag | Identifies a specific representation |
| Last-Modified | Indicates when a resource was last changed |
| Vary | Controls which request headers affect the cached response |
Why HTTP Caching Matters
Without caching, browsers and intermediary systems may need to request the same resources repeatedly. Proper caching allows reusable responses to be served without contacting the origin server every time.
- Reduce network requests.
- Improve page loading performance.
- Reduce server workload.
- Lower bandwidth consumption.
- Improve application scalability.
- Provide faster access to static resources.
How HTTP Caching Works
When a browser requests a resource, the server can include caching headers in its response. The browser or an intermediary cache stores the response according to those instructions. When the same resource is requested again, the cache can either return the stored response directly or contact the server to determine whether the cached response is still valid.
Client
↓
Request resource
↓
Origin server
↓
Response + caching headers
↓
Browser / CDN cache
↓
Future request
↓
Cached response or validationCache-Control
Cache-Control is the main HTTP header used to control caching behavior. It supports multiple directives that determine whether a response can be cached, how long it remains fresh and whether caches should revalidate it.
Cache-Control: max-age=3600In this example, a shared response can be considered fresh for 3600 seconds according to the max-age directive. The exact behavior also depends on the other directives present and the type of cache handling the response.
Common Cache-Control Directives
| Directive | Meaning |
|---|---|
| max-age | Maximum freshness lifetime in seconds |
| s-maxage | Freshness lifetime for shared caches |
| public | Response may be stored by shared caches |
| private | Response is intended for a private cache |
| no-cache | Stored response must be revalidated before reuse |
| no-store | Response should not be stored |
| must-revalidate | Stale response must be revalidated |
| immutable | Resource is not expected to change while fresh |
max-age
The max-age directive specifies the maximum amount of time, in seconds, that a cached response can remain fresh.
Cache-Control: max-age=86400A value of 86400 represents one day. During its freshness lifetime, a cache can generally reuse the response without contacting the origin server for validation.
no-cache vs no-store
The names of these directives are easy to confuse. no-cache does not mean that a response cannot be stored. It indicates that the stored response must be validated before reuse. no-store, on the other hand, instructs caches not to store the response.
| Directive | Storage | Reuse |
|---|---|---|
| no-cache | May be stored | Must be revalidated |
| no-store | Should not be stored | Cannot be reused from a cache |
public and private
The public and private directives help describe whether a response can be stored by shared caches. A private response is generally intended for a specific user and should not be reused by shared caches for other users.
Cache-Control: public, max-age=3600
Cache-Control: private, max-age=600Caching Static Assets
Static assets such as JavaScript, CSS, fonts and images are often good candidates for caching because they are requested frequently and can usually be versioned when their contents change.
Cache-Control: public, max-age=31536000, immutableLong-lived caching works especially well when filenames contain a content hash or another version identifier. When the asset changes, the URL changes as well, allowing the new version to be requested without relying on cache invalidation.
Cache Busting with Versioned Assets
Cache busting means changing a resource URL when its contents change. This allows a browser to safely cache an older URL for a long time while requesting a new URL after deployment.
/app.a82f91.js
/app.c14b77.jsETag
An ETag is a validator that identifies a particular representation of a resource. When a cached response becomes stale or needs validation, the client can send the ETag value back to the server using the If-None-Match request header.
ETag: "abc123"
If-None-Match: "abc123"If the resource has not changed, the server can respond with HTTP 304 Not Modified instead of sending the complete response body again.
ETag Validation Flow
| Step | Action |
|---|---|
| 1 | Server returns a response with an ETag |
| 2 | Browser stores the response |
| 3 | Cached response requires validation |
| 4 | Browser sends If-None-Match |
| 5 | Server compares the validator |
| 6 | Server returns 304 or a new response |
First request
↓
200 OK + ETag
↓
Cached response
↓
If-None-Match
↓
304 Not ModifiedLast-Modified
Last-Modified tells the client when the server believes a resource was last changed. During validation, the browser can send the value using the If-Modified-Since request header.
Last-Modified: Wed, 12 Aug 2026 10:00:00 GMT
If-Modified-Since: Wed, 12 Aug 2026 10:00:00 GMTIf the resource has not changed since the supplied date, the server can return 304 Not Modified and omit the response body.
ETag vs Last-Modified
ETag and Last-Modified are both cache validators, but they identify changes differently. ETag uses an identifier for the resource representation, while Last-Modified communicates a modification timestamp.
| Feature | ETag | Last-Modified |
|---|---|---|
| Validator | Entity tag | Modification date |
| Request header | If-None-Match | If-Modified-Since |
| Response header | ETag | Last-Modified |
| Typical purpose | Representation validation | Time-based validation |
Expires
The Expires header provides an absolute date and time after which a cached response is considered stale. It is an older caching mechanism and is generally less flexible than Cache-Control.
Expires: Wed, 19 Aug 2026 10:00:00 GMTModern applications typically prefer Cache-Control for cache lifetime configuration, although Expires may still appear for compatibility with older clients and infrastructure.
Cache-Control vs Expires
| Feature | Cache-Control | Expires |
|---|---|---|
| Syntax | Directive-based | HTTP date |
| Flexibility | High | Limited |
| Modern usage | Preferred | Legacy / compatibility |
| Typical example | max-age=3600 | Specific expiration date |
Vary Header
The Vary header tells caches which request headers can change the representation returned by the server. This prevents a cached response generated for one request variant from being incorrectly served to another request with different characteristics.
Vary: Accept-EncodingFor example, if a server generates different representations depending on the Accept-Encoding request header, a cache needs to keep those variants separate.
Caching API Responses
API responses require more careful caching decisions because their contents may depend on authentication, user identity, permissions, query parameters or rapidly changing application data.
| Resource | Possible Strategy |
|---|---|
| Public API data | Short public cache lifetime |
| User-specific data | Private cache or no-store |
| Frequently changing data | Short freshness lifetime |
| Immutable reference data | Long cache lifetime |
Caching Authenticated Responses
Responses containing private user information require special attention. A response generated for one authenticated user should not accidentally become available to another user through a shared cache.
Cache-Control: private, no-cacheFor especially sensitive responses, applications may choose no-store when storing the response in a cache is not appropriate.
Fresh and Stale Responses
A cached response has a freshness lifetime determined by the applicable caching rules. A fresh response can generally be reused without validation, while a stale response may require revalidation or may need to be fetched again depending on the cache directives.
| State | Typical Behavior |
|---|---|
| Fresh | Can generally be reused without validation |
| Stale | May require validation or a new request |
304 Not Modified
The 304 Not Modified response tells a client that the cached representation is still valid. The server does not need to send the complete response body again, which can save bandwidth and reduce response size.
HTTP/1.1 304 Not Modified
ETag: "abc123"Browser Cache vs CDN Cache
A browser cache is associated with an individual user agent, while a CDN or proxy cache can store responses for many users. Cache-Control directives can therefore affect private browser caching and shared caching differently.
| Cache | Typical Location | Main Benefit |
|---|---|---|
| Browser cache | User's device | Fast local reuse |
| CDN cache | Edge server | Reduced origin requests |
| Proxy cache | Network intermediary | Shared response reuse |
s-maxage
The s-maxage directive is intended for shared caches and can specify a freshness lifetime that differs from max-age. This can be useful when a response should be cached for one period by browsers and another period by shared infrastructure.
Cache-Control: public, max-age=60, s-maxage=3600Caching and Dynamic Content
Dynamic pages are not automatically unsuitable for caching. If the generated response is identical or safely reusable for multiple users, a controlled caching strategy can provide significant performance improvements. The important consideration is whether cached content can safely be reused for another request.
Common Caching Mistakes
- Using long cache lifetimes for frequently changing content.
- Caching private user-specific responses in shared caches.
- Confusing no-cache with no-store.
- Forgetting to version static assets.
- Using outdated Expires rules instead of appropriate Cache-Control directives.
- Ignoring the effect of Vary on cache keys.
- Failing to validate cache behavior after deployment.
- Caching sensitive responses without considering authorization.
Caching Best Practices
- Use Cache-Control as the primary caching mechanism.
- Use long-lived caching for versioned immutable assets.
- Use shorter lifetimes for frequently changing resources.
- Use private for user-specific responses when browser caching is appropriate.
- Use no-store when a response should not be stored.
- Use ETag or Last-Modified for efficient validation when appropriate.
- Configure Vary when different request headers produce different representations.
- Review caching behavior for authenticated and sensitive responses.
- Test caching behavior through browser developer tools and HTTP clients.
Practical Cache-Control Examples
Different resources often require different caching policies. There is no single Cache-Control value that is correct for every response.
# Public static asset
Cache-Control: public, max-age=31536000, immutable
# Short-lived public response
Cache-Control: public, max-age=300
# User-specific response
Cache-Control: private, max-age=300
# Response that should not be stored
Cache-Control: no-store
# Cached but validated before reuse
Cache-Control: no-cacheHow to Debug Caching
When caching behaves unexpectedly, inspect the complete HTTP response and request headers. Browser developer tools can show whether a resource was loaded from the memory cache, disk cache or network, while command-line tools can reveal the actual headers returned by the server.
curl -I https://example.com/app.jsLook for Cache-Control, ETag, Last-Modified, Expires and Vary headers. Also check whether redirects, service workers, CDNs or application-level caching introduce additional behavior.
Frequently Asked Questions
What is the most important HTTP caching header?
Cache-Control is generally the primary header used to define caching behavior because it supports directives for freshness, storage, validation and shared caches.
What is the difference between no-cache and no-store?
no-cache allows a response to be stored but requires validation before reuse, while no-store instructs caches not to store the response.
What does ETag do?
ETag identifies a specific representation of a resource and allows clients to validate cached content using the If-None-Match request header.
What is the purpose of the Expires header?
Expires specifies an absolute expiration date for a cached response. Cache-Control is generally preferred for modern cache configuration.
Should API responses be cached?
Some API responses can be safely cached, especially public or relatively stable data. User-specific, sensitive or authorization-dependent responses require much more careful caching rules.
What is a 304 response?
304 Not Modified tells the client that its cached representation is still valid, allowing it to reuse the stored response without downloading the complete body again.
Helpful HTTP Tools
A Cache-Control Generator creates Cache-Control headers with common caching directives, an HTTP Header Generator helps build complete HTTP header sets, an HTTP Header Viewer displays response and request headers during debugging, an ETag Generator creates ETag values for testing cache validation, and an Expires Header Generator produces correctly formatted expiration headers.
Conclusion
HTTP caching headers provide the rules that determine how browsers, CDNs and other caches store and reuse responses. Cache-Control is the primary mechanism for defining freshness and storage behavior, while ETag and Last-Modified provide efficient validation and Expires offers an older date-based caching mechanism. By choosing cache policies according to how resources change, whether they are safe to share and how they are versioned, developers can reduce network traffic, improve application performance and avoid serving outdated or private content incorrectly.