How to Cache WordPress Pages on Cloudflare’s Free Plan
In this article12 sections
To cache WordPress pages on Cloudflare’s free plan, proxy your domain through Cloudflare, create one Cache Rule that marks your blog pages as eligible for cache, and exclude every page that must stay dynamic: the admin area, the login page, search, feeds, and any visitor who is logged in or has left a comment. Cloudflare does not cache HTML by default, so without this rule every page view still travels to your server. With the rule, anonymous visitors get a stored copy from a Cloudflare data center near them.
On the SiteHarbour blog, cached pages answered in about 0.03 seconds, while pages that had to reach the server took between 0.27 and 0.92 seconds. This guide shows the exact rule, how to test it, and the trade-off you accept. It sits on top of a server cache such as the one in our Nginx FastCGI cache guide, and it will not fix a site that is slow for other reasons, which this diagnosis guide helps you find.
What Cloudflare caches by default
By default Cloudflare stores static files such as images, stylesheets and scripts, chosen by file extension. It does not store HTML pages, because for many sites the same address can show different content to different people. A WordPress page is HTML, so each visit is passed to your server, which runs PHP and MySQL to build it again. That is the work a Cache Rule removes.
A Cache Rule says: for requests matching this expression, treat the response as cacheable and keep it for this long. The matching part is the important one. A rule that matches too much can show one visitor’s page to another, and a rule that matches too little never produces a HIT.
Before you start
- Proxy the DNS record. In Cloudflare’s DNS tab, the record for your site must show an orange cloud (Proxied). A grey cloud (DNS only) sends visitors straight to your server, and no rule applies.
- Know your paths. This guide assumes WordPress lives in
/blog/on the same domain as another application. If WordPress is at the site root, change/blog/to/in the expression. - Check that your server sends no cookies to anonymous visitors. Pages that set cookies are often not stored. Step 1 shows you how to check.
- Plan how you will purge. A cached page stays until its timer runs out. Purging automatically when you publish is covered in a companion guide, and you should set it up soon after this one.
Step 1: Check what Cloudflare does today
Request the page and read the cache header. Replace example.com with your domain:
U=https://example.com/blog/
curl -s -o /dev/null -D - "$U" | grep -iE '^(cf-cache-status|age|set-cookie):'
The header cf-cache-status tells you what happened. DYNAMIC means Cloudflare does not consider the page cacheable, which is expected before you add the rule. If no set-cookie line appears, that is good news, because it means the page is the same for everyone.
Step 2: Create the Cache Rule
- In the Cloudflare dashboard, open your domain, go to Caching, then Cache Rules, and click Create rule.
- Name it
Blog pages at the edge. - Under If incoming requests match, choose Custom filter expression, then click Edit expression to switch to the plain text box.
- Paste the expression below, with your own domain:
(http.host eq "example.com" and starts_with(http.request.uri.path, "/blog/") and http.request.uri.query eq "" and not starts_with(http.request.uri.path, "/blog/wp-admin") and not starts_with(http.request.uri.path, "/blog/wp-json") and not http.request.uri.path in {"/blog/wp-login.php" "/blog/wp-cron.php" "/blog/xmlrpc.php" "/blog/wp-comments-post.php"} and not http.request.uri.path contains "/feed" and not http.request.uri.path contains "sitemap" and not http.cookie contains "wordpress_logged_in" and not http.cookie contains "wordpress_sec" and not http.cookie contains "comment_author" and not http.cookie contains "wp-postpass")
Warning. Paste the expression into the expression editor, not into a “URI Full” or wildcard box. A wildcard field treats your text as a literal web address, so the rule saves without errors but never matches anything. If your page stays DYNAMIC after the rule is saved, check this first.
Then set the cache behavior:
- Under Cache eligibility, choose Eligible for cache.
- Under Edge TTL, choose Ignore cache-control header and use this TTL, and set it to 1 day.
- Leave Browser TTL on its default, so browsers still check with Cloudflare.
- Click Deploy. If Cloudflare warns that the rule may not apply to your traffic, review the expression, then choose to deploy anyway.
The free plan has a minimum edge timer, which was 2 hours at the time of writing, so do not set it much lower than that. WordPress sends no cache headers for public pages, which is why this rule tells Cloudflare to ignore them and use its own timer.
What each condition protects
- The host and path conditions limit the rule to the blog. A Laravel or other application on the same domain is not touched.
- The empty query string condition skips every address with a query string. That covers search (
?s=), previews and tracking parameters, and it keeps the rule simple. - The path exclusions keep the admin area, the REST API, the login page, WordPress’s scheduled-task file, XML-RPC, the comment form handler, feeds and sitemaps away from the cache.
- The cookie conditions send logged-in users, visitors who unlocked a password-protected post, and commenters straight to your server. This stops a page built for one person, such as one with an admin bar or a name filled into a comment form, from being stored and shown to others.
Step 3: Test it
Request the page twice. The first request should be a MISS, which means Cloudflare fetched the page from your server and stored it. The second should be a HIT, and age shows how many seconds the copy has been stored:
for i in 1 2; do curl -s -o /dev/null -D - "$U" | grep -iE '^(cf-cache-status|age):'; echo ---; done
Then confirm that pages which must stay dynamic never return a HIT:
for p in "/blog/?s=test" "/blog/feed/" "/blog/wp-login.php" "/blog/wp-json/"; do
echo "$p $(curl -s -o /dev/null -D - "https://example.com$p" | grep -i '^cf-cache-status' | tr -d 'r')"
done
curl -s -o /dev/null -D - -H "Cookie: wordpress_logged_in_test=1" "$U" | grep -i '^cf-cache-status'
Anything except HIT is correct for these requests. Finally, measure the speed difference yourself:
for i in 1 2 3; do curl -s -o /dev/null -w '%{time_starttransfer}sn' "$U"; done
The number is the time to first byte. The first request may be a MISS and slower, and the others should be much faster.
The trade-off: pages can be out of date
A stored copy stays until its timer ends or you purge it. If you edit a post, visitors can keep seeing the old version for up to a day. The same applies to sidebars and related links: when you publish a new article, other pages that list recent posts show the old list until their copies expire. You have three ways to handle this:
- Purge by URL when you publish. This is the best option, and the companion guide shows how to automate it.
- Use a shorter timer if you publish often and cannot purge.
- Accept a small delay on a site that changes rarely.
Cloudflare and your server cache work together
You do not have to choose between the two layers. A server cache, as in the FastCGI cache guide, makes a MISS cheap, because your server answers from disk instead of running PHP. Cloudflare’s copy means most requests never reach your server at all. When you clear caches by hand, clear the server first and Cloudflare second, otherwise Cloudflare can fetch the old server copy again.
One side effect to know about: your server logs now show Cloudflare’s addresses instead of your visitors’ addresses. The note in our scanner probes guide explains why you should not ban those addresses.
If the page stays DYNAMIC
- Confirm the DNS record is proxied (orange cloud).
- Open the rule and check that the expression is in the expression editor and ends with a closing bracket.
- Look at the order of your rules. Another rule above yours that bypasses cache can win, and an old Page Rule can also interfere.
- Check that the page does not send a
set-cookieheader. - Test the exact URL you matched, including the trailing slash.
Common mistakes to avoid
- Leaving the DNS record grey instead of orange.
- Pasting the expression into a wildcard field. The rule then matches nothing.
- Caching addresses with query strings. One visitor’s search results could be shown to another.
- Forgetting the commenter and logged-in cookies.
- Using “Purge Everything” for every change. It clears every application on the domain and sends all visitors back to your server at once.
- Testing while logged in. Use a private window or
curl.
Where this fits with your hosting
Cloudflare adds an edge layer in front of any host. If your WordPress site runs on one of our shared hosting plans, the server-side page cache comes from LiteSpeed Cache, which you can set up with our 5-step speed guide. Add the Cloudflare rule after that is working.
Related guides: monitoring VPS bandwidth with vnStat and setting up free SSL on Nginx with Certbot. If something breaks, see fixing ERR_TOO_MANY_REDIRECTS in WordPress.
Frequently asked questions
Is the free plan enough for this?
Yes. Cache Rules are included on the free plan, and the free plan allows several of them. One rule is all this guide needs.
Will logged-in users see cached pages?
No. The cookie conditions send them to your server, so the admin bar and your dashboard always show live content.
Why is the first visit still slow?
Each Cloudflare data center stores its own copy. The first visitor served by a data center gets a MISS, and later visitors served by that data center get the stored copy.
Does caching HTML affect search engines?
Search crawlers receive the same copy that any anonymous visitor receives. A faster response helps them crawl your site, as long as the copy is not badly out of date.
Do I need a plugin?
Not for the rule. For automatic purging you need a small piece of code or a plugin, which the companion guide covers.
The short version: proxy the DNS record, create one Cache Rule in the expression editor, exclude admin, login, search, feeds and logged-in cookies, test for MISS then HIT, and set up purging so edits show up quickly.