Ctrl + K
HTTP12 min read

Caching Headers Explained

Understand HTTP caching headers, browser and intermediary caches, Cache-Control directives, validators and practical caching strategies.

Published: 2026-09-02

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.

HeaderPrimary Purpose
Cache-ControlDefines caching rules and freshness
ExpiresProvides an expiration date
ETagIdentifies a specific representation
Last-ModifiedIndicates when a resource was last changed
VaryControls 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 validation

Cache-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=3600

In 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

DirectiveMeaning
max-ageMaximum freshness lifetime in seconds
s-maxageFreshness lifetime for shared caches
publicResponse may be stored by shared caches
privateResponse is intended for a private cache
no-cacheStored response must be revalidated before reuse
no-storeResponse should not be stored
must-revalidateStale response must be revalidated
immutableResource 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=86400

A 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.

DirectiveStorageReuse
no-cacheMay be storedMust be revalidated
no-storeShould not be storedCannot be reused from a cache
⚠️ Do not use no-cache when your actual requirement is to prevent storage. Use no-store when a response must not be stored by caches.

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=600

Caching 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, immutable

Long-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.js
💡 Long cache lifetimes are much safer for versioned static assets because a changed file receives a new URL instead of relying on every cache to immediately forget the previous version.

ETag

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

StepAction
1Server returns a response with an ETag
2Browser stores the response
3Cached response requires validation
4Browser sends If-None-Match
5Server compares the validator
6Server returns 304 or a new response
First request
    ↓
200 OK + ETag
    ↓
Cached response
    ↓
If-None-Match
    ↓
304 Not Modified

Last-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 GMT

If 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.

FeatureETagLast-Modified
ValidatorEntity tagModification date
Request headerIf-None-MatchIf-Modified-Since
Response headerETagLast-Modified
Typical purposeRepresentation validationTime-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 GMT

Modern 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

FeatureCache-ControlExpires
SyntaxDirective-basedHTTP date
FlexibilityHighLimited
Modern usagePreferredLegacy / compatibility
Typical examplemax-age=3600Specific 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-Encoding

For 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.

ResourcePossible Strategy
Public API dataShort public cache lifetime
User-specific dataPrivate cache or no-store
Frequently changing dataShort freshness lifetime
Immutable reference dataLong cache lifetime
⚠️ Never assume that an API response is safe to share through a shared cache. User-specific or authorization-dependent responses require careful cache-control configuration.

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-cache

For 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.

StateTypical Behavior
FreshCan generally be reused without validation
StaleMay 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.

CacheTypical LocationMain Benefit
Browser cacheUser's deviceFast local reuse
CDN cacheEdge serverReduced origin requests
Proxy cacheNetwork intermediaryShared 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=3600

Caching 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.
💡 A good caching strategy is usually based on how frequently a resource changes, whether it is safe to share and whether its URL can be versioned when the content changes.

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-cache

How 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.js

Look 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.

Found an issue?

Found an error, outdated information, or something missing from this article? Let me know through the Contact page.

Your feedback helps improve our articles and keep them accurate and useful.