Question 5 of 6
Content not Being Cached
Customer ticket
Subject: Content not Being Cached
After activating Cloudflare on my site, I am seeing that resources on my pages are not being cached: […] Why is Cloudflare’s caching not working on my site?
< HTTP/1.1 200 OK
< Content-Type: text/html; charset=utf-8
< Transfer-Encoding: chunked
< Connection: keep-alive
< Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0, max-age=0
< Strict-Transport-Security: max-age=10
< Server: cloudflare-nginx
< CF-RAY: 1fa4b922fef517a4-SIN
{ [3507 bytes data]1. Email reply to the customer
Hi there,
Thanks for including the response headers, which made this much quicker to diagnose. Caching is working as designed here. This particular response is uncacheable for two separate reasons, and both can be changed if you want it cached.
Why this response isn’t cached
- It’s an HTML page. By default Cloudflare caches static files by file extension (images, CSS, JS, fonts, PDFs and so on), but not HTML. HTML is often personalised (logged-in pages, carts), so we only cache it when you explicitly tell us to.
- Your origin is telling us not to cache it. The header
Cache-Control: no-store, no-cache, must-revalidate, …, max-age=0comes from your web server or application (it’s the default for many PHP/CMS setups). Cloudflare respectsno-store,privateandmax-age=0, so even if HTML were eligible, this response would not be stored.
Your images, CSS and JavaScript are most likely being cached already. You can confirm by requesting one directly and looking for cf-cache-status:
curl -svo /dev/null https://example.com/css/style.css 2>&1 | grep -i -E "cf-cache-status|cache-control|age:"
# cf-cache-status: HIT (or MISS on the first request, then HIT)For the HTML page itself, current responses show cf-cache-status: DYNAMIC, which means “not eligible for cache”. The headers you sent show Server: cloudflare-nginx and no cache-status header, which suggests they were captured some time ago or from a cached tool result. Our edge now returns server: cloudflare. Could you rerun the request and send fresh output? The -SIN at the end of the CF-RAY also shows the request was handled in our Singapore data centre, so please let me know if that’s expected for where you tested from.
If you want your HTML cached
Only do this for pages that are the same for every visitor. You have two options, and they work well together:
- Fix it at the origin (best long-term): send cache-friendly headers for public pages, for example
Cache-Control: public, max-age=300, or keep browsers short and give the CDN longer withCache-Control: public, max-age=60, s-maxage=3600(or aCDN-Cache-Controlheader). Avoid sendingSet-Cookieon these pages, as it prevents caching too. - Create a Cache Rule in Cloudflare (Caching > Cache Rules, available on all plans and replacing the older “Cache Everything” Page Rule):
- When incoming requests match: e.g. hostname equals
example.comand URI path does not start with/wp-admin,/account,/cart, or whatever your private areas are. You can also exclude requests carrying a login or session cookie. - Cache eligibility: Eligible for cache.
- Edge TTL: Ignore cache-control header and use this TTL (for example 1 hour). This is required while your origin still sends
no-store; otherwise the response showsBYPASS.
- When incoming requests match: e.g. hostname equals
(http.host eq "example.com" and not starts_with(http.request.uri.path, "/wp-admin")
and not http.cookie contains "wordpress_logged_in")After deploying, request the page twice: the first response should show MISS and the second HIT. When you publish changes, you can purge the URL (or everything) under Caching > Configuration. Also make sure Development Mode is off, as it bypasses the cache.
One last unrelated point I noticed: Strict-Transport-Security: max-age=10 means browsers only remember the HSTS policy for 10 seconds, so it gives almost no protection. Once you’re confident HTTPS works everywhere, you could raise it gradually (for example to 6 months or a year).
Feel free to send me the URLs you’d like cached and I’ll gladly review the rule with you.
Kind regards,
Marwan
Cloudflare Customer Support
2. Thought process
I read the headers line by line, since they contain the whole story:
Content-Type: text/htmlmeans the resource is HTML, which isn’t in Cloudflare’s default cached extensions. The current docs say this explicitly (“does not cache HTML or JSON by default”).Cache-Control: no-store, no-cache, …, max-age=0means the origin forbids caching. According to the docs,no-storeprevents caching in either Origin Cache Control mode, so even “Eligible for cache” givesBYPASSunless the Edge TTL overrides origin headers.- No
CF-Cache-StatusplusServer: cloudflare-nginx: modern responses showDYNAMICfor uncached HTML andserver: cloudflare, so this capture is likely old. I ask for fresh output rather than assume. CF-RAY …-SINmeans it was served from Singapore. That’s worth checking against where the customer tested from, but I keep it to a light question.HSTS max-age=10is a side observation. It isn’t the question, but it’s worth a friendly mention.
The customer said “resources on my pages” but showed only the HTML document, so I separate the two: static assets are probably fine (and I show how to check), while HTML needs an explicit opt-in. I steer them to Cache Rules rather than Page Rules, since Page Rules are now marked as deprecated in the docs, and I warn about caching personalised pages. That’s the real risk with “cache everything”.
3. Tools I would use, and why
| Tool | Why |
|---|---|
curl -svo /dev/null <url> | Shows the response headers (cf-cache-status, cache-control, age, set-cookie) for both HTML and static assets. |
Browser DevTools > Network | Lets the customer see cache status for every resource on the page at once. |
Cloudflare dashboard: Caching > Cache Rules | Make HTML eligible for cache and override the origin TTL safely, with exclusions. |
Cloudflare dashboard: Caching > Configuration | Check Development Mode and caching level, and purge after changes. |
Cloudflare Trace (Rules > Trace) | Confirms which Cache Rule matches a given URL. |
Analytics > Caching / Cache Analytics | Shows cache hit ratio and which content types are served dynamically, before and after the change. |
Origin config (e.g. nginx, Apache, CMS) | Where the no-store header comes from, and the long-term fix. |