The HTTP Cache-Control header sits at the center of browser caching, CDN behavior, and edge caching policy. Used well, it reduces origin load, improves repeat visits, stabilizes TTFB, and makes cache behavior more predictable across browsers, reverse proxies, and managed caching solutions. Used poorly, it creates stale content, broken logins, or assets that never seem to update. This reference is written as a practical guide for developers and IT teams who need clear rules, useful examples, and a repeatable review process for keeping caching headers current as applications, CDNs, and delivery requirements change.
Overview
If you only remember one thing about Cache-Control, remember this: the header is a policy language for deciding who may cache a response, for how long, and under what conditions it can be reused. That policy affects browsers, shared intermediaries such as CDNs, and reverse proxy cache layers differently depending on the directives you send and the surrounding headers.
For most websites, the practical goal is simple. Cache static assets aggressively, cache HTML carefully, bypass or restrict personalized responses, and make validation or purging predictable. That is true whether you run a simple brochure site, a WordPress install, an API behind a reverse proxy cache, or a multi-layer CDN for websites with global traffic.
The most commonly used directives are these:
max-age: how long a response can be treated as fresh by any cache that is allowed to store it.s-maxage: a shared-cache override, commonly relevant for CDNs and proxy caches.public: the response may be stored by shared caches.private: the response is intended for a single user and should not be stored by shared caches.no-cache: the response may be stored, but it must be revalidated before reuse.no-store: do not store the response in any cache.must-revalidate: once stale, the cache must validate before serving it.stale-while-revalidate: a stale response may be served briefly while the cache refreshes it in the background.stale-if-error: a stale response may be served when the origin is unavailable or errors.immutable: the response is not expected to change during its freshness lifetime, commonly used for versioned static assets.
Those directives are more useful when mapped to real content types:
- Versioned CSS, JS, images, fonts: usually long-lived caching with
public, a longmax-age, and oftenimmutable. - HTML pages: often short-lived or validated caching, sometimes with edge-specific behavior through
s-maxage. - Logged-in dashboards: usually
privateorno-store. - API responses: vary widely; public APIs may be edge-cacheable, while user-specific APIs often require careful
privateor bypass rules.
A useful mental model is to separate freshness, storage, and scope. Freshness is controlled by values such as max-age. Storage is controlled by directives such as no-store. Scope is controlled by public and private. Once you think in those three dimensions, most cache-control headers become easier to design and troubleshoot.
Examples help:
Cache-Control: public, max-age=31536000, immutableThis is a standard pattern for fingerprinted static files such as app.4f3c9a.js. The file name changes when the content changes, so long browser caching is safe.
Cache-Control: public, max-age=60, s-maxage=300, stale-while-revalidate=30This pattern is useful for HTML or non-personalized API responses when you want browsers to refresh often but want the CDN to keep serving a fresh-enough copy longer.
Cache-Control: private, no-cacheThis can fit user-specific pages that may be stored in a browser cache but should be validated before reuse. In many sensitive cases, teams choose no-store instead.
Cache-Control: no-storeThis is the safe option for highly sensitive content such as account, checkout, or admin responses where any storage risk is unacceptable.
One more practical point: Cache-Control does not operate alone. You will often pair it with validators such as ETag or Last-Modified, and with routing logic in your CDN, reverse proxy cache, or application. If you are tuning Cloudflare caching, a reverse proxy cache, or WordPress caching, the header policy and the cache platform rules need to agree.
Maintenance cycle
This topic benefits from a maintenance mindset because caching rules age quickly as your application changes. New routes appear, login flows change, CDNs add behavior, and developers ship assets differently. A header strategy that worked six months ago may now be leaving performance on the table or caching the wrong thing.
A practical review cycle is quarterly for most teams, with a lighter monthly check for production-critical properties. The point is not to rewrite every header often. It is to verify that your rules still match your content types and delivery stack.
A repeatable maintenance cycle can look like this:
- Inventory response classes. List your main categories: HTML, static assets, APIs, authenticated pages, cart and checkout routes, admin surfaces, media files, and any machine-to-machine endpoints.
- Map desired cache scope. For each class, decide whether browser-only, shared cache, or no caching is appropriate.
- Set freshness targets. Decide how long each class should remain fresh in browsers and at the edge. This is where
max-ageands-maxagebecome intentional rather than inherited defaults. - Check invalidation paths. Confirm that changed content can be refreshed either through URL versioning, validation, or cache purge workflows.
- Test actual behavior. Inspect response headers in the browser, through command-line requests, and via your CDN dashboard or logs if available.
- Review exceptions. Make sure login, search, cart, preview, and admin routes are not accidentally cached in shared layers.
For many teams, the biggest win comes from separating browser caching from edge caching. A common mistake is treating them as the same problem. They are related, but not identical. Browsers are close to the user and benefit from long asset lifetimes. CDNs and reverse proxies help absorb traffic and improve TTFB, but they also need safe rules for personalized and dynamic content.
When you review your policy, ask these questions:
- Are static assets versioned in their URLs? If yes, can you safely extend browser caching?
- Are HTML pages being cached too aggressively in the browser and causing stale navigations?
- Could the CDN keep public HTML or API responses longer than the browser using
s-maxage? - Are validators present where revalidation is preferable to full bypass?
- Do purge workflows exist for CMS changes, deployments, and image replacements?
If you run WordPress caching, maintenance is especially important because plugin changes, query-string behaviors, logged-in sessions, and WooCommerce cache rules can alter what should be cached. A default stack may work on launch day and drift out of alignment after theme updates, new plugins, or marketing scripts are added.
For teams comparing providers, your maintenance cycle should also include a platform check. Not every CDN for websites interprets edge rules in the same way, and not every shared cache gives you the same visibility. If you are evaluating vendors, our comparisons on Cloudflare vs Bunny.net vs Fastly and this roundup of the best CDN services for small business websites can help frame the operational side of header strategy.
Signals that require updates
You do not need to wait for a formal review date to revisit cache-control headers. Several production signals should trigger an immediate check because they usually point to a mismatch between header policy and real application behavior.
1. Users see old content after deployments.
This usually means one of two things: static assets are not versioned, or HTML is cached longer than intended. If your CSS or JS file names stay the same after release, a long max-age becomes risky. In that case, switch to file fingerprinting or shorten cache lifetimes.
2. Cache hit ratio is low for content that should be cacheable.
If public assets or anonymous pages miss too often, inspect whether responses are being marked private, whether query strings fragment the cache key, or whether cookies are causing bypass at the edge. Low hit ratio usually means policy drift, not just traffic shape.
3. TTFB spikes even when origin capacity looks healthy.
This can happen when caches are validating too frequently or not storing responses at all. Review no-cache versus no-store, and make sure s-maxage is used when edge caching should be more permissive than browser caching.
4. Authenticated pages leak into shared cache.
This is a high-priority issue. Review route matching, cookie-based bypass rules, and whether responses that contain user-specific content are marked private or no-store. If you use a reverse proxy cache, verify that cache keys vary correctly and that sensitive cookies prevent storage where needed.
5. CDN behavior does not match browser behavior.
This often happens when teams assume the browser and the edge interpret the same policy identically in all cases. Shared cache rules can differ, especially once platform-level settings enter the picture. Always test through the CDN, not just locally in DevTools.
6. API consumers report inconsistent freshness.
APIs often deserve their own caching profile. Public read-heavy endpoints may benefit from short edge TTLs and stale-while-revalidate. User-specific or write-sensitive endpoints often need tighter controls. If your API caching strategy has grown organically, that is a sign to refactor.
7. A platform migration changes caching semantics.
Moving from a basic origin setup to a managed CDN, from one provider to another, or from browser-only caching to an edge delivery network often changes how headers are respected. Re-verify before and after the cutover.
If monitoring is part of your stack, pair header reviews with live metrics. Articles like Monitoring Cache Performance for Live Analytics and Predictive Cache Monitoring are useful complements because headers only matter if the resulting cache behavior is actually observable.
Common issues
Most caching problems are not caused by one bad directive. They come from a mismatch between application intent, header values, and cache platform behavior. These are the issues developers run into most often.
Confusing no-cache with no-store.no-cache does not mean “do not cache.” It means the cached response must be revalidated before reuse. no-store is the true “do not store this anywhere” instruction. Mixing them up leads either to unnecessary origin traffic or to content being stored when the team expected a full bypass.
Using long TTLs without asset versioning.
Long-lived browser caching is excellent for performance only when changed assets also get new URLs. If your deployment process does not fingerprint files, keep TTLs conservative or you will create update lag for returning users.
Caching HTML like static assets.
Public HTML can often be cached at the edge, but browser caching for HTML usually needs more restraint. A short browser lifetime plus a longer shared cache lifetime is often a safer balance than one long TTL everywhere.
Ignoring shared-cache control.
Teams sometimes set only max-age and miss the opportunity to tell shared caches to behave differently via s-maxage. That leaves CDN performance gains on the table.
Not planning for stale serving.stale-while-revalidate and stale-if-error can smooth out origin latency and brief failures for public content. They are not appropriate everywhere, but they are worth evaluating for pages and endpoints that benefit from resilience more than absolute immediacy.
Forgetting cookie interactions.
Many CDNs and reverse proxy cache systems reduce or skip caching when cookies are present. If marketing scripts, consent tools, or application middleware add cookies broadly, cacheability can drop without any header change.
Assuming cache purge is optional.
TTL alone rarely solves everything. Editorial teams update images, developers hotfix scripts, and product teams need urgent content changes. If you cannot purge reliably, your header strategy is incomplete.
Applying one policy to every route.
A modern website usually needs different cache policies for assets, landing pages, search results, API reads, previews, and authenticated experiences. Uniform policy is easy to implement but rarely optimal.
A simple starting matrix can help:
- Fingerprinted static assets:
public, max-age=31536000, immutable - Anonymous HTML pages: short browser TTL, possibly longer
s-maxage, optionalstale-while-revalidate - Logged-in/account routes:
privateorno-store - Cart/checkout/admin: typically
no-store - Public API reads: short TTLs with validation or edge overrides as appropriate
If your environment includes specialized workloads, content-specific strategies matter even more. For example, public dashboards, operational logs, or multi-surface SaaS apps often need different freshness profiles across views. Related guidance in Cache Strategy for All-in-One SaaS Platforms, Cache Strategy for Time-Series Dashboards, and Caching Real-Time Operational Logs can help you think beyond generic site pages.
When to revisit
Treat this header reference as something to revisit on a schedule and at key moments in your delivery lifecycle. Caching is never fully “done” because your application is not static. New routes, plugins, APIs, traffic patterns, and CDN settings can all change what good cache-control looks like.
Revisit your Cache-Control strategy when any of the following happens:
- A new CDN, reverse proxy cache, or edge platform is introduced.
- Your deployment process changes asset naming or bundling.
- You launch authenticated features, carts, or previews on routes that were previously public.
- You add APIs that are read-heavy or latency-sensitive.
- Your cache hit ratio falls, origin traffic rises, or purge requests become frequent.
- You notice stale content complaints after releases.
- You migrate CMS, theme, plugin, or hosting infrastructure.
For an action-oriented review, use this short checklist:
- Pick five representative URLs: homepage, a content page, a static asset, a logged-in page, and one API endpoint.
- Fetch headers directly and through the CDN.
- Confirm whether the response should be cacheable by browser, edge, both, or neither.
- Verify that
max-age,s-maxage, and storage directives match that intent. - Test what happens after a content change or deployment.
- Confirm there is a purge or versioning path for anything long-lived.
- Document the rule set so developers do not guess on future routes.
If you need a broader infrastructure review, comparing platforms is often useful because visibility and controls differ across providers. Our pieces on CDN features and pricing comparisons and how to evaluate managed caching vendors can help you pair correct headers with the right operational tooling.
The best outcome is not a perfect one-time header string. It is a living caching policy that your team understands, can test, and can update without drama. If you review it regularly, the Cache-Control header becomes less of a source of surprises and more of a reliable lever for website speed optimization, safer browser caching, and stronger edge caching performance.