Cache-Control Headers Explained: A Practical Guide to Browser, CDN, and Origin Caching
CDNHTTP CachingCache-ControlEdge CachingWeb PerformanceDeveloper Guide

Cache-Control Headers Explained: A Practical Guide to Browser, CDN, and Origin Caching

CCache Cloud Editorial
2026-08-07
7 min read

Learn how Cache-Control headers govern browser and CDN caching, with practical policies for assets, HTML, APIs, and private content.

Cache-Control headers determine whether a response is stored, how long it remains fresh, and which caches may reuse it. This guide explains the directives that matter most for browser caching, CDN caching, reverse proxy caching, and origin behavior, with reusable examples for static assets, HTML, APIs, and authenticated content.

Overview

HTTP caching works across several layers: the user’s browser, an intermediary such as a reverse proxy, and a CDN edge location. Each layer can reduce requests to the origin, improve time to first byte (TTFB), and make pages feel more responsive. However, a cache is useful only when it serves the right version of a resource.

Cache-Control is the primary instruction set for managing that trade-off. It can allow a response to be stored, define its freshness lifetime, require validation before reuse, or prohibit storage altogether. A good policy starts with the content’s update frequency and sensitivity rather than with a blanket “cache everything” rule.

For broader implementation decisions, compare these header policies with your wider cache TTL strategy by content type. TTLs are only one part of a reliable CDN caching design; cache keys, bypass rules, purging, and deployment workflows matter as well.

Core framework

Public and private responses

public indicates that a response may be stored by shared caches, including CDNs and reverse proxies. It is commonly appropriate for resources that are not personalized, such as versioned CSS, JavaScript, fonts, and public images.

private tells shared caches not to store the response, while still allowing a browser to cache it when other directives permit. Use it for user-specific pages or responses containing account information. A private response may still be reused locally by the same user, but it should not become a shared CDN object.

Freshness with max-age and s-maxage

max-age sets the maximum freshness lifetime, in seconds, for caches. It generally applies to browser and shared caches. For example:

Cache-Control: public, max-age=3600

This allows a public response to be considered fresh for one hour. A browser or CDN can serve it without contacting the origin during that period, subject to its own configuration and normal cache behavior.

s-maxage sets a separate freshness lifetime for shared caches. It is useful when a CDN should retain an object longer than a browser should. When present, it takes precedence over max-age for shared caches:

Cache-Control: public, max-age=300, s-maxage=86400

In this example, a browser may reuse the response for five minutes, while a CDN may serve it for one day. Set these values only when your publishing and purge process can tolerate the resulting delay.

no-cache is not no-store

no-cache does not mean “do not cache.” It means a stored response must be validated with the origin before reuse. Validation may use an ETag or Last-Modified value, allowing the origin to confirm that the cached object is still current.

Cache-Control: public, no-cache

no-store is stronger: it instructs caches not to store the response. Use it for content that should not be retained by browser or shared caches, such as highly sensitive responses or one-time transactional results. It can reduce caching benefits, so apply it deliberately rather than as a default for every dynamic page.

Serving stale content safely

stale-while-revalidate permits a cache to serve a response after its normal freshness period while it checks for an updated version in the background:

Cache-Control: public, max-age=60, s-maxage=600, stale-while-revalidate=60

Here, the object is fresh for ten minutes at a shared cache. After that, the cache may serve the stale object for up to another minute while revalidating. This can smooth traffic spikes and avoid making every visitor wait for an origin request, but it means some users may briefly see an older version.

Related directives can tighten behavior. must-revalidate tells a cache not to serve a stale response once it is no longer fresh. immutable can be appropriate for assets whose URLs change whenever their content changes, although it should not be used on an unversioned file that may be replaced at the same URL.

Practical examples

Versioned static assets

For files such as app.8f31c.js or styles.4c2a.css, use a long lifetime when the filename changes whenever the content changes:

Cache-Control: public, max-age=31536000, immutable

With this pattern, a new deployment publishes a new URL instead of waiting for every browser and CDN to forget the old file. See the practical guidance on caching static assets for faster Core Web Vitals before applying long TTLs to files with stable, unversioned URLs.

HTML pages

Public HTML can often use a shorter shared-cache lifetime than static assets:

Cache-Control: public, max-age=60, s-maxage=300, stale-while-revalidate=60

This policy can reduce origin requests while limiting how long a CDN serves an older page. If the page contains personalized elements, account details, or session-specific output, use a private policy or configure careful edge-side rules rather than treating it as universally public.

Public API responses

An API response that is identical for many users may be cacheable, but its freshness and cache key need explicit design:

Cache-Control: public, max-age=30, s-maxage=120
Vary: Accept-Encoding

Keep the TTL short when the underlying data changes frequently. Include relevant request dimensions in the cache key, and avoid caching responses affected by authorization, cookies, or private request headers unless the edge configuration safely separates those variants. For a fuller API caching workflow, see how to cache APIs safely.

Authenticated or sensitive responses

For account pages, checkout steps, and responses containing private data, a conservative starting point is:

Cache-Control: private, no-store

Whether both directives are necessary depends on the intended behavior, but the example makes the policy explicit: do not retain a shared copy and do not store the response locally. Test login, logout, impersonation, and session transitions with browser developer tools and CDN logs.

Common mistakes

  • Using long TTLs without versioning: A file replaced at the same URL can remain stale until its browser and CDN entries expire or are purged.
  • Assuming a CDN follows every header automatically: CDN products may have override rules, minimum TTLs, query-string settings, or bypass conditions. Confirm the effective response headers at the edge.
  • Confusing cache storage with cache freshness: A response can be stored but require revalidation. Inspect Age, CDN cache-status headers, and origin logs rather than relying on one header.
  • Ignoring Vary and cache keys: Compression, language, device, cookies, and authorization can produce different representations. A shared cache must distinguish variants safely.
  • Purging without fixing the policy: A purge removes existing objects, but the next response may recreate the same problematic TTL or cache key. Correct the response headers and edge rules first.
  • Caching HTML indiscriminately: A fast page is not a successful result if one visitor receives another visitor’s personalized content. Start with public, deterministic responses.

If a supposedly cacheable response is repeatedly missed, use a structured CDN cache MISS troubleshooting checklist. Also review whether cookies, query strings, or request headers are triggering a deliberate bypass.

When to revisit

Revisit your Cache-Control headers whenever the content model, deployment process, or CDN configuration changes. A site that moves from server-rendered pages to an application shell, adds personalization, introduces a new API, or changes asset naming may need a different policy even if the URLs look familiar.

Make header review part of these practical checkpoints:

  1. After changing the CDN provider, reverse proxy, cache key, or cache bypass rules.
  2. After introducing user accounts, regional content, language variants, or cookie-based personalization.
  3. After changing asset versioning or the release and rollback process.
  4. After adding an API endpoint or changing the freshness expectations of existing data.
  5. When monitoring shows an unexpected cache hit ratio, stale content, origin load, or TTFB pattern.
  6. When relevant HTTP caching standards or CDN features change and your implementation depends on them.

For a repeatable audit, inspect representative responses with curl -I from the origin and through the CDN, compare Cache-Control, ETag, Vary, and cache-status headers, then test both a first request and a repeat request. Record the intended policy for each content type, document the purge path, and verify that private responses never appear in a shared cache. This small inventory turns cache-control headers from a collection of defaults into an explicit edge caching strategy.

Related Topics

#CDN#HTTP Caching#Cache-Control#Edge Caching#Web Performance#Developer Guide
C

Cache Cloud Editorial

Technical SEO Editorial Team

Senior editor and content strategist. Writing about technology, design, and the future of digital media. Follow along for deep dives into the industry's moving parts.