Map the policy to the resource, not the server
Inventory representative URLs before changing headers: a home page, content page, application shell, CSS, JavaScript, images, fonts, APIs, login, account, checkout, and downloads. For each, record whether the response is public or personalized, whether its URL changes when its bytes change, and how fast a correction must reach visitors.
Make a policy matrix from that inventory. A public content-addressed asset needs a different rule from HTML at a stable address. Responses without Cache-Control can still be cached heuristically, so explicit rules are easier to test.
The period a stored response may be reused; max-age gives it in seconds.
Use Cache-Control terms precisely
Cache-Control tells browsers and shared caches how to store and reuse a response. max-age=N permits reuse while fresh. s-maxage=N sets shared-cache freshness and overrides max-age there, but browsers ignore s-maxage. public permits any cache; private permits browser caching but prevents shared-cache storage.
no-cache permits storage but requires origin validation before reuse. no-store means caches must not store the response, but is not a complete privacy control. Use must-revalidate when serving stale content after expiry is unacceptable; an unavailable origin can then yield a 504 rather than a stale fallback.
It removes storage rather than merely requiring a check; reserve it for responses that genuinely must not be stored.
Version immutable assets before assigning a long lifetime
A versioned asset URL includes a content fingerprint or release version, such as app.4f2c.js. When asset bytes change, publish a different URL and update the HTML reference. The cache sees a new resource, so visitors fetch the new asset without an invalidation reaching every browser.
For a representation that will not change during its freshness lifetime, Cache-Control: max-age=31536000, immutable is a practical pattern. immutable tells supporting clients not to conditionally revalidate while fresh. Do not apply it or a year-long max-age to a mutable same-URL file. Retain prior fingerprinted assets while cached HTML can still reference them.
HTML chooses asset URLs, so release sequencing must retain the referenced generation.
Make HTML current through validation, not long caching
HTML usually has a stable, human-facing URL and changes with content, asset references, feature flags, or security notices. A conservative public HTML pattern is Cache-Control: no-cache with ETag and Last-Modified when your platform can produce them correctly. The browser stores the body but uses a conditional request before reuse; an unchanged response can return 304 Not Modified without a body.
ETag identifies a resource version. On a stale response, the browser can send it in If-None-Match; if it matches, the server replies 304, otherwise it sends the new representation. RFC guidance prefers both ETag and Last-Modified on a 200 response when possible. no-cache does not promise validation on Back or Forward navigation because a browser may restore a session-history snapshot.
An ETag must change when the representation changes; test it across origins and deployments.
Separate public delivery from personalized and sensitive responses
A shared cache, such as a CDN, may reuse one response for multiple users. Do not let it reuse account-specific HTML or API data across users. For personalized content that may live in a browser, private, no-cache is a sensible starting point: it prevents shared storage and requires validation. A Set-Cookie header alone does not stop caching.
For responses that should not be stored, use no-store and review application code, CDN rules, service workers, browser storage, logs, and error pages. Headers alone are not a privacy, compliance, or payment-security program. Have qualified security, privacy, and payment reviewers assess sensitive flows and applicable requirements.
Test sign-in, sign-out, account switching, and permission changes with warm browser and CDN caches.
Roll out cache changes as a reversible release
Capture baseline headers and body behavior for uncached and repeat requests. In staging, deploy versioned assets first, then HTML that references them, and test older cached HTML against the new origin. Check direct-origin and CDN paths if both exist. DevTools Network headers should show the intended policy and repeat behavior.
Keep a release manifest of asset URLs, header rules, and configuration version. If a release combines incompatible client files, restore compatible HTML and application behavior, but do not overwrite a published fingerprinted file. Repoint HTML to the known-good generation and retain both generations while cached HTML may request them. A shared-cache purge cannot force browsers to discard a fresh response.
Turn header-test failures, asset 404s, and version mismatches into changes to the next release checklist.
