Technology

CDN Cache-Control for WordPress and WooCommerce

A CDN can make public pages quick while an incorrect rule shares a personal cart or account response. WordPress and WooCommerce need a policy that decides which representations can be shared before deciding how long to keep them. Short TTLs do not make personalized HTML safe for shared caching.

This guide focuses on Cache-Control semantics, browser versus edge freshness, and a WooCommerce cache policy to validate against your site. Header examples are conditional starting points for inspected content, not settings that work unchanged on every CDN. Vendor rules and origin page caches must enforce the same privacy and variation boundaries.

Why CDNs Love Static Files but Hesitate on HTML

Let’s start with the basics. A CDN is brilliant at moving static files—images, CSS, JS—closer to your visitors. No database calls, no PHP, no waiting around. With WordPress, those assets are easy wins: cache them for a long time and your site already feels faster. HTML is trickier. It changes more often, it can be personalized, and in WooCommerce it depends on who’s logged in, what’s in the cart, what coupons are applied, and twenty other tiny decisions.

Only inspected public, non-personalized HTML belongs in a shared cache. Anonymous visitors can still have carts, location prices or plugin personalization, so “not logged in” is insufficient. Apply request-time bypass and response-time storage checks, then select acceptable freshness for the genuinely public representations.

Think of it like ordering coffee at a cafe. The regular latte doesn’t need to be made from scratch every time; it’s a standard recipe that’s easy to speed up. But your friend’s oat milk, extra-hot, decaf, triple-shot situation? That needs special attention. WooCommerce dynamic paths are that custom order. We just need to label them clearly so the CDN doesn’t try to pre‑make them for everyone.

Cache-Control in Plain English

Cache-Control defines HTTP cacheability and freshness, while CDN rules may override or implement those directives differently. Separate intended semantics from observed behavior. A header does not prove safety when a Worker, page cache or vendor minimum TTL can still serve a previously stored representation.

Decoding the usual suspects

Here are the main directives and their roles:

max-age gives freshness to caches generally; s-maxage overrides it for shared caches and is ignored by browsers. public allows shared storage, while private prohibits shared storage but can allow browser caching. no-cache requires validation before reuse; no-store prohibits storage. must-revalidate requires validation after freshness expires. stale-while-revalidate and stale-if-error grant bounded stale reuse where supported; they do not make private content shareable.

For a deeper reference, see the MDN guide to Cache-Control directives.

Headers for public versioned assets

For public assets whose URL changes whenever their bytes change, a long freshness period can be appropriate. Fingerprinted filenames are straightforward; query versioning works only if every cache preserves the version in its key:

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

immutable says the representation will not change during its freshness lifetime, not that it is valid forever. Keep older asset URLs available while cached HTML still references them. A CDN purge cannot evict a browser’s long-lived copy; if bytes change under the same URL, use a shorter policy or switch to real versioning.

Headers for public WordPress HTML (non-WooCommerce)

For blogs, brochure sites, or content that doesn’t depend on sessions, I like smart edge caching with quick revalidation:

Cache-Control: public, max-age=0, s-maxage=1800

For an inspected public response, this example makes the browser copy immediately stale and allows shared freshness for 1,800 seconds. Thirty minutes is suitable only if that delay is acceptable and invalidation works. It grants no explicit stale window. Adding stale-while-revalidate to this same Cache-Control header can also affect browsers; use a documented edge-specific control when only the CDN should serve stale. Revalidation may be answered by an intermediary with a valid fresh copy rather than reaching origin PHP.

Headers for WooCommerce HTML

Public product or category URLs can still return member pricing, location taxes, currency variants, mini-carts or personal widgets. Inspect the full response and headers. Separate verified public variants or bypass; do not rely solely on absence of login/cart cookies, since the first response may create a session.

Set a suitable short shared TTL only for validated public catalog responses. Keep sensitive responses out of shared cache with explicit request-time bypass and a restrictive response policy, for example:

Cache-Control: private, no-store

The HTTP meaning prohibits storage, but verify the effective CDN configuration. Fastly does not use no-store alone to prevent its own caching; private plus explicit pass/bypass is needed for this policy. CloudFront Minimum TTL must not override sensitive-response handling. Changing headers does not remove an already leaked cache object; disable the faulty rule and purge affected representations.

Edge Rules That Keep You Fast Without Breaking WooCommerce

Edge rules are where the magic happens. Your Cache‑Control headers tell caches how to behave, but edge rules decide when to cache and when to step aside. This is where we describe “latte” versus “triple‑shot decaf oat flat white.”

Paths to bypass

Keep the following WordPress URLs outside shared caching under this policy:

1) /wp-admin/ and /wp-login.php — always bypass and don’t even try to be clever. 2) /wp-cron.php — definitely bypass; it’s not a page. 3) /wp-admin/admin-ajax.php — bypass entirely; it’s the workhorse for many plugins. 4) Preview and customizer URLs — anything with preview=true or customize_changeset_uuid should bypass.

WooCommerce endpoints that must stay dynamic

Bypass the store’s actual localized/custom cart, checkout, account, order-pay, order-received and payment-return paths and endpoints. /cart/, /checkout/ and /my-account/ are examples. Bypass wc-ajax, wc-api and add-to-cart actions regardless of query order. Store API cart/checkout calls can be GET and still session-specific; include /wp-json/wc/store/ and alternative rest_route forms for these endpoints.

Bypass authentication/session contexts before cache lookup. Include wordpress_logged_in_, wp_woocommerce_session_, woocommerce_cart_hash and woocommerce_items_in_cart; match variable-suffix cookie families correctly. Inspect wp-postpass_, commenter-prefill cookies and extension-specific membership/currency behavior. Bypass Authorization requests too. Prevent Set-Cookie responses from filling shared cache in this general policy, including the first session-creating response, and apply the exclusions to origin page caches.

Query strings and hit rate

Exclude tracking parameters only after proving they do not change the response body, redirects or headers. A campaign plugin may use UTM data to personalize a page. Preserve content-changing language, currency, filter and pagination inputs, and review or bypass unknown parameters. Removing a parameter from the key differs from stripping it from the browser URL or origin request; test analytics separately.

HTML caching for anonymous users only

Use an allowlist of inspected public GET routes, with sensitive path, action, cookie and authorization exclusions that take precedence. An “anonymous HTML” switch needs verification of its exact behavior. Do not use a broad substring expression followed by “else cache everything”; it can miss localized routes, first-session responses and new plugin endpoints.

Different vendors name this differently—page rules, cache rules, policies—but the idea is the same. If you need a quick orientation on how one popular provider structures this, the Cloudflare cache rules overview explains the available controls. In Cloudflare Cache Rules, the last matching rule wins for conflicting settings; ensure a later broad rule cannot undo bypass. Custom key features depend on plan, and Workers need separate review.

Respecting ETag and Last-Modified

ETag or Last-Modified can support conditional requests and 304 responses when the origin generates them correctly for the representation. Do not assume WordPress provides suitable validators on every HTML route. Test changes, variants and compression so a stale or mismatched validator cannot validate the wrong response.

A Practical Walkthrough: From WordPress Basics to a WooCommerce Store

The following rollout starts with a small public scope and expands after correctness checks. Keep a written route/cookie inventory and a rollback path. Changes to payment, membership, language or cache plugins require the policy to be checked again.

Step 1: Make static assets a home run

Cache public versioned assets first and verify that every content change produces a new URL reflected in HTML. Preserve query versions if they are the chosen scheme. Restrict extension-based rules to real static paths; a route ending in .css can still return dynamic HTML. Deploying a new file or purging the CDN does not force a browser to discard an unchanged long-lived URL.

Step 2: Cache HTML for anonymous users (non‑WooCommerce)

Start with inspected non-personalized articles and a freshness window the content can tolerate. Coordinate publish/update invalidation at the origin and CDN, including dependent archives. Confirm the new body actually appears; purge requests and short TTLs do not guarantee instant global visibility. Add stale behavior only with explicit content-age and browser/edge decisions.

Step 3: Bypass for logged‑in users and sensitive paths

Bypass admin, login, preview, customizer, password-access and user-specific routes and contexts. A fast response is not proof that the CDN stored it; inspect cache status and body over repeated GETs. Cookies and nonces need correct isolation even when the surrounding template looks public.

Step 4: WooCommerce—cache the catalog, protect the cart

Expand to catalog pages only after checking session data and location/language/currency/member variants. Actions and Store API cart/checkout calls stay dynamic even when the request has no cookie yet. Test two customers in both fill orders: personal then anonymous, and anonymous then personal. Verify totals, stock visibility, coupon handling and account isolation.

Step 5: Normalize marketing parameters

For parameters proven irrelevant to the representation, use the provider’s selective key controls if supported. Keep the browser URL intact when client analytics needs it and test server-side attribution too. Search pages can remain outside the initial allowlist; if later cached, preserve the search query and all functional filters with a suitable freshness policy.

Step 6: Automate cache purging on content changes

Map content dependencies before automating invalidation. A changed product can appear in categories, homepage panels, related products and language/currency variants. Invalidate origin HTML first so the edge cannot refill from a stale page-cache copy. Tags/surrogate keys can group these dependencies where supported; a broad theme change may justify a broad purge. Measure refill load and verify content after the purge.

Real‑World Header Recipes You Can Borrow

These examples describe HTTP response headers, not complete Nginx/Apache configurations. Apply them only after the route and privacy checks above; confirm the provider actually honors the intended policy and has no conflicting override.

Static assets

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

Use for public files with immutable versioned URLs. If the same URL can change, a long browser max-age plus CDN purge is insufficient; use new URLs or a suitable shorter freshness/revalidation policy.

Public WordPress HTML (anonymous users)

Cache-Control: public, max-age=0, s-maxage=1800

This is an example for inspected non-personalized HTML that can tolerate up to thirty minutes of shared freshness. It is not a general catalog default. Adjust the interval to stock, price and publishing requirements, and validate purges and variants. Stale permissions are intentionally separate so browser behavior is not accidentally broadened.

WooCommerce cart/checkout/account

Cache-Control: private, no-store

Use with effective CDN and origin-page-cache bypass, not as a substitute for it. Include cart fragments, session-specific Store API, wc-ajax/wc-api and first responses that create a session. Confirm no broad rule or provider-specific behavior stores or serves a personal response.

API and admin endpoints

Cache-Control: private, no-store

Use this conservative policy plus bypass for admin and unreviewed/private API endpoints. Public product APIs may be assessed separately; GET alone is not evidence of a public response. Preserve authentication and authorization checks independently of caching.

Optional: Surrogate‑Control for CDNs that support it

Fastly supports Surrogate-Control for edge policy separately from the browser’s Cache-Control. For verified public content only, its documented pattern can keep browser copies immediately stale while granting the edge a bounded freshness/revalidation window:

Cache-Control: public, max-age=0
Surrogate-Control: max-age=1800, stale-while-revalidate=60

Fastly prefers Surrogate-Control over Cache-Control for supported edge freshness directives. Its Surrogate-Control reference redirects to the current documentation. This is not a portable header for every CDN. Keep explicit request bypass and private response controls; in Fastly, no-store and Authorization alone are not sufficient edge-cache protection. Confirm browser-facing headers and service-chain behavior.

Cookies, Variants, and the Subtle Ways Caches Go Sideways

I wish I could say all plugins play by the same rules, but they don’t. Some drop cookies on every visitor. Others add a toolbar for certain roles or produce tiny bits of page personalization. That’s fine, but it means you have to be deliberate about when to cache and when to bypass.

Bypass on cookie, not Vary: Cookie

Vary: Cookie can create many variants where honored, while personal session requests are better excluded under this policy. Do not remove an existing Vary header merely to improve hit rate: first establish what changes the response and encode safe variants or bypass. CDN Vary support differs, so header presence alone is not proof of isolation.

Device variants

If you’re using a responsive design (most themes are), avoid device‑specific cache keys. Don’t vary on user‑agent unless you truly need separate mobile/desktop content. Every variant is a smaller, colder cache. Keep it simple and let CSS handle small differences.

CDN default behavior versus your intent

Defaults differ by CDN and custom code. Introduce HTML caching only for an inspected allowlist with effective exclusions and response privacy checks. Test with separate sessions, first-session responses, GET bodies and origin-cache indicators. A bypassed CDN request may still receive a stale or wrongly shared page from an origin cache.

Testing and Troubleshooting

Use reproducible tests with independent guest, cart and authenticated sessions. Record both body content and headers, and test every critical transition rather than relying on one anonymous page load.

Check headers with curl

From your terminal:

curl -sS -D - -o /dev/null https://example.com/
curl -I https://example.com/

The first command sends GET and discards its body after receiving it; the second checks HEAD for comparison. Repeat GETs and inspect the actual body separately in controlled sessions. curl does not inherit browser login cookies. Check Cache-Control, Vary, Set-Cookie, Age and the provider’s cache status, using the store’s real paths. Keep session cookies out of shared command history and logs.

Simulate edge behavior

A HIT shows reuse, while a MISS can populate the cache for a later request. Repeated MISS may mean cold variants, purges, eviction or non-cacheability; it does not alone diagnose a bad rule. Age is estimated response age, not remaining TTL. If a personal response is shared, disable the affected cache policy and purge the exposed representations before retesting.

Watch the query strings

Compare requests with and without the candidate parameter, including body, redirects and response headers. Only consolidate verified equivalent responses. Then test analytics and repeated cache behavior. An improved hit ratio is useful only if the stored representation remains correct.

Fight double caching carefully

Origin page caches and CDNs can coexist if privacy exclusions, variants and invalidation agree. A CDN bypass does not bypass a plugin cache automatically. Invalidate from origin toward edge and inspect both layers during tests; otherwise the CDN can refill with old HTML even after a successful purge.

Cart Fragments and Background Requests

A cached catalog can still create origin work through background cart requests. Since WooCommerce 7.8, legacy cart-fragment loading is limited to relevant widget/dependency/explicit-enqueue cases rather than every page by default. Themes or extensions can still add it. Inspect the actual request trace and widget dependencies before deciding a call is unnecessary.

Bypassing a required AJAX request protects correctness but does not eliminate its origin cost. Do not disable fragments for all anonymous visitors: guests also have carts, and mini-cart updates may depend on them. Remove unnecessary enqueueing or consider a compatible Mini-Cart Block implementation, then test add/remove items, multiple tabs and cached pages. No instant performance gain is guaranteed.

Putting It All Together: A Simple, Reliable Blueprint

A WordPress/WooCommerce rollout checklist should cover these decisions:

1) Cache only public static assets with a valid versioning policy. 2) Allow inspected non-personalized HTML with explicitly chosen browser/edge freshness. 3) Bypass actual sensitive paths, actions, private APIs and session/authorization contexts. 4) Reject personal and Set-Cookie responses from shared storage. 5) Preserve functional key variants; exclude only proven irrelevant parameters. 6) Purge origin and edge dependencies on updates. 7) Test GET bodies in two sessions, first-cookie behavior, stale limits and cold-cache load.

If you want to go deeper into edge patterns tailored for WordPress, see the companion guide covering HTML caching and bypass tricks you can adapt right away: CDN Caching Rules for WordPress: The Friendly Guide to HTML Caching, Bypass Tricks, and Edge Settings That Won’t Break WooCommerce. It pairs nicely with this article when you’re ready to fine‑tune.

Advanced Touches When You’re Ready

Once your baseline is solid, there are a few upgrades that feel like flipping a turbo switch.

Stale TTLs for resilience

Bound stale reuse by what the content can tolerate and by documented provider support. A blog article and a time-sensitive promotion can need very different limits. Cache-Control stale directives can also affect browsers; use an edge-specific mechanism when appropriate. Do not use stale delivery for private data or as a substitute for a correct checkout failure response.

Tag‑based purging

Use tags to represent actual invalidation dependencies: the product, listings, widgets and relevant variants. Purge only a product URL when that is genuinely the full effect; otherwise include the dependent pages. Provider support and completion timing differ, so confirm fresh content and origin refill load.

Smart cache keys

Custom keys must reproduce the application’s representation choices, including relevant language, currency and location. Do not treat all marketing parameters as irrelevant by name. Bounded safe variants help hit rate; unknown personalization should be reviewed or bypassed.

Monitor what’s actually happening

Monitor hit ratios alongside origin errors, route latency and actual cart/order correctness. Plugin changes can introduce new cookies or endpoints, but an unexpected bypass can be a necessary privacy protection. Investigate the response before changing a rule merely to restore the old hit rate.

Common Gotchas and How to Avoid Them

1) Vary:* and Vary:Cookie are different. Under HTTP rules, Vary:* always prevents a stored response from matching a new request; Vary:Cookie can create cookie-specific variants where supported. Do not strip either blindly. Find the representation dependency and implement correct cache isolation or bypass.

2) Preview and customizer responses can contain unpublished content and must bypass under this policy. Match the actual preview/action parameters, not only one literal preview=true string. Keep search outside the initial allowlist; later inclusion needs its full query/filter key and a tested freshness rule.

3) Caching admin-ajax. A blanket “cache everything” rule can include dynamic AJAX responses. Explicitly bypass /wp-admin/admin-ajax.php and all wc-ajax endpoints.

4) Query-key consolidation is conditional. Exclude only parameters proven not to affect content, redirects or headers; preserve actions and useful variants, and test campaign measurement after the change.

5) Choose purge scope from the change’s full dependencies. Selective purge helps a small edit; global template or policy changes may require broad invalidation. A stale private object needs removal even if clearing it creates refill load.

If You’re New to CDNs, A Gentle Ramp

A gradual rollout makes incorrect assumptions easier to find:

Start with public versioned assets, establish measurements, then add a small set of non-personalized HTML pages. Expand only after route, session, first-cookie, update and variant tests pass. Keep WooCommerce interactions dynamic. Advanced keys or stale behavior need the same correctness evidence, not simply a week or two without a complaint.

If you want a broad overview of how CDNs actually help and where they sit in your stack, this friendly explanation of what a CDN is and why it matters makes a good side read once you’re done here.

Edge Rules in Human Language

Write the policy in plain language first, then map every condition to supported vendor behavior:

1) Share only inspected public GET responses. 2) Bypass admin/login/cron/AJAX, previews and customizer actions. 3) Bypass actual localized cart/account/checkout/payment endpoints and session-specific Store API calls. 4) Bypass action queries, authentication/session/personalization cookies and Authorization. 5) Prevent personal and Set-Cookie responses from filling shared cache. 6) Preserve relevant variants and review unknown parameters. 7) Cache only the remaining allowlisted responses with explicit freshness, effective rule precedence and tested purge. Apply the same exclusions to origin page caches.

This is a conservative policy framework, not a claim of percentage completeness. Inventory the actual site and extensions, and test effective behavior at every cache layer before rollout.

What About Security Headers and HTTPS?

HTTPS and security headers must remain correct on cached responses. In a CSP nonce design, header and HTML must agree; sharing the same nonce in cached pages needs separate design review. HSTS requires working HTTPS across its chosen scope, and origin TLS validation matters as well as visitor-to-CDN TLS. A one-click control is not evidence that the complete path is correctly configured.

Wrap‑Up: Your Site, Only Faster—and Still Correct

Successful caching means public responses can be reused without mixing sessions or hiding unacceptable content changes. Establish that property through headers, body inspection, two-customer tests and verified invalidation. Performance gains then follow from reduced work where cache hits actually occur; they do not fix every checkout or background-job bottleneck.

Begin with versioned assets and a small public HTML allowlist. Add explicit sensitive-context bypass, verified key variants, acceptable freshness and dependency-aware purges. Keep the policy and tests readable so another operator can reproduce them after a plugin or CDN change.

Record the tested routes, cookie families, provider settings and expected responses. Recheck them as the site evolves; correctness and freshness need ongoing evidence as much as speed does.


Further reading that fits right into this topic:

• The official docs are handy when you want to double‑check directive behavior: how Cache‑Control works and a vendor view like Cloudflare’s cache rules overview. Recheck the relevant behavior when the provider, plugins or cache policy changes.

Frequently Asked Questions

Only inspected public, non-personalized responses should enter shared cache. Anonymous users can still have session data. Use actual route/action/API and cookie/authorization exclusions, prevent personal Set-Cookie responses from filling cache, and test origin caches too.

Use long immutable freshness only for truly versioned public assets. A public HTML example is max-age=0 with a separately chosen s-maxage. Sensitive responses need private, no-store plus effective bypass. Provider overrides and stale permissions must be checked.

Only after proving they do not change the body, redirects or response headers. Preserve functional parameters and review unknown ones. Key changes do not necessarily remove parameters from the browser URL; test analytics and session correctness separately.
↑