Well-configured cache-control headers help browsers and CDNs reuse safe responses without serving private or outdated content. This practical workflow shows how to choose TTL settings by asset type, protect personalized pages, verify behavior at each caching layer, and invalidate content safely when releases change.
Overview
Cache-Control headers are instructions attached to an HTTP response. They tell a browser, shared cache, CDN, or reverse proxy whether a response may be stored, how long it may be reused, and what to do when it becomes stale. Correct headers are an important part of website caching because they reduce repeat requests to the origin while preserving the freshness and privacy users expect.
The right policy depends on the response, not simply on its file extension. A versioned JavaScript file can usually be cached for a long period. An account page, shopping cart, or API response containing user-specific data generally should not be stored in a shared cache. HTML documents often need a shorter lifetime than static assets because they reference changing content and deployment-specific files.
Cache headers work alongside, rather than replace, CDN cache rules and origin configuration. A CDN may apply its own default TTL, override origin instructions, or cache content according to a path-based rule. Before changing settings, map the complete request path: browser, DNS and CDN layer, reverse proxy, application server, and data store. The distinction between page cache, object cache, and CDN edge cache is explained in this website caching overview.
Step-by-step workflow
1. Inventory response types
Start with a small inventory of the responses your site serves. Group URLs by behavior and sensitivity:
- Fingerprint or versioned assets such as
app.abc123.js, CSS, fonts, and images. - HTML pages that are public but change during publishing.
- Public API responses with a defined freshness window.
- Authenticated, personalized, or transactional responses.
- Error pages, redirects, feeds, webhooks, and other special responses.
Do not assume that every response under a public URL is safe to cache. Check for login state, cookies, authorization headers, account data, cart contents, and experiments that change the response. For practical bypass patterns, see the guide to excluding login, cart, and personalized pages from cache.
2. Choose a policy for each group
Use the narrowest policy that meets the performance goal. Common patterns include:
Cache-Control: public, max-age=31536000, immutable
This is suitable for static assets whose URLs change whenever their contents change. The long browser lifetime is safe because a new filename or query strategy is used for each build. The immutable directive signals that a fresh check is unnecessary while the response remains fresh. It should not be applied to a URL that can change in place.
Cache-Control: public, max-age=300, s-maxage=600
This pattern gives browsers a shorter freshness period while allowing a shared CDN cache to reuse the response longer. It can work for public HTML or API data when a delay of several minutes is acceptable. Confirm that your CDN honors s-maxage and does not replace it with a dashboard-level rule.
Cache-Control: no-cache
no-cache does not mean “do not store.” It permits storage but requires a cache to validate the response before reuse. This is often useful for HTML that should be rechecked while still benefiting from validators such as ETag or Last-Modified.
Cache-Control: private, no-cache
Use a private policy for responses intended only for an individual browser, such as a signed-in dashboard. The private directive helps prevent shared caches from storing the response. For highly sensitive responses, a stricter no-store policy may be appropriate:
Cache-Control: no-store
Use no-store deliberately for data that should not be stored by caches. It is not a substitute for access control, encryption, or secure application design.
3. Set freshness and stale behavior separately
TTL settings answer how long a response is considered fresh. Directives such as stale-while-revalidate and stale-if-error address what may happen after freshness expires:
Cache-Control: public, max-age=60, s-maxage=300, stale-while-revalidate=30, stale-if-error=86400
With a policy like this, a shared cache may serve a recently stale response while it fetches an update, and may use an older response during an origin failure if the caching layer supports those directives. Treat these values as operational decisions: stale content may improve resilience, but it must be acceptable for the page or API involved.
4. Add validators and vary only when needed
Validators reduce transfer size when a cache rechecks an object. Your web server or application may generate an ETag or Last-Modified response header. Make sure deployments do not produce unstable validators for unchanged content, and check that conditional requests return a correct 304 Not Modified response.
Use the Vary header only for request attributes that genuinely change the representation, such as a supported compression format or a carefully controlled language selection. Varying on cookies or other high-cardinality values can fragment the cache and create unexpected misses. If a response varies by authentication or session state, bypassing shared cache is often safer than trying to create many cache variants.
5. Choose a cache-busting and invalidation plan
For static assets, prefer content-hashed filenames or an equivalent versioning scheme. Deploy the new assets first, then publish HTML that references them. Keep old assets available long enough for already-open pages and delayed clients to finish loading.
For HTML and API responses, decide whether to wait for TTL expiry, purge selected URLs, purge a tag or surrogate key, or purge the entire zone. Broad purges are simple but can send a large request surge to the origin. A release runbook should identify which URLs change, which cache layer is purged, who approves the action, and how the result is verified. The deployment-focused guide How to Bust Cache Safely During Deployments covers this workflow in more detail.
Tools and handoffs
Assign each setting to the layer that owns it. The application or web server should define the semantic policy for the response. The CDN should implement edge behavior, purge controls, and any documented overrides. The browser follows the response headers it receives, subject to its own cache behavior. Keeping these responsibilities explicit prevents a CDN rule from silently defeating an origin policy.
Use browser developer tools to inspect the actual response headers and identify whether the request was served from memory, disk, the network, or an edge cache. From a terminal, a header-only request is a useful first check:
curl -I https://example.com/assets/app.abc123.js
curl -I https://example.com/
Repeat tests with a cache-busting query only when you understand how the CDN treats query strings. Compare the first request with subsequent requests and inspect headers such as Age, ETag, Last-Modified, Vary, and any CDN-specific cache-status header. Header names and meanings differ by provider, so document the interpretation used by your team.
For a complete site review, use the cache headers, TTLs, and purging checklist. If assets repeatedly miss at the edge, compare the request URL, cookies, query strings, authorization headers, and rule order; the CDN cache-miss troubleshooting guide provides a useful diagnostic path.
Quality checks
- Check privacy: Request signed-in, signed-out, and different-user versions of the same URL. Confirm that one user’s content cannot appear in another user’s response.
- Check freshness: Publish a controlled content change and measure how long old HTML or API data remains visible at the browser, CDN, and origin.
- Check deployment order: Verify that referenced versioned assets are available before new HTML is released.
- Check cache keys: Test meaningful query parameters, trailing slashes, hostnames, compression, language, and cookies. Confirm that irrelevant variations are not causing unnecessary misses.
- Check failure behavior: If stale serving is enabled, test an origin error in a controlled environment and confirm that private or transactional responses are excluded.
- Check performance: Compare origin requests, time to first byte, cache-hit behavior, and Core Web Vitals before and after the change. A cache policy is successful only if it improves delivery without creating correctness problems. Use this TTFB and Core Web Vitals checklist for broader testing.
Record the intended policy beside the observed policy. A simple table containing URL pattern, response type, browser TTL, shared-cache TTL, bypass conditions, purge method, and owner makes later troubleshooting much faster.
When to revisit
Review cache-control headers whenever the application’s data sensitivity or release process changes. Revisit them after moving to a new CDN, enabling a new reverse proxy, changing hosting architecture, introducing localization or experimentation, or adding authentication to previously public routes. A platform migration can change default TTLs and rule precedence even when the application code is unchanged.
Also review policies when users report stale content, a deployment produces missing assets, cache-hit rates fall, origin traffic rises, or a personalized response appears in a shared context. These symptoms often indicate a mismatch between the origin header, CDN rule, cache key, and invalidation process.
As an action plan, inventory your top response types, assign a policy to each, test public and private paths with curl and browser tools, then document the purge and rollback procedure. Schedule the review alongside major infrastructure or application releases rather than treating cache headers as a one-time configuration task. That habit keeps website caching fast, predictable, and safe as the rest of the delivery stack evolves.