Improve WordPress Performance With Cloudflare

The Bent Nail

Dynamic websites are difficult to cache safely.

WordPress

This site runs on WordPress with an efficient hosting setup, and I run most of my sites through Cloudflare. One of Cloudflare’s main features is caching. You can improve WordPress performance with Cloudflare.

By default, Cloudflare caches static content, like images, CSS, and JS files. And that makes them fast! But the page itself, even if nothing changed, isn’t cached for faster performance. Cloudflare would have to get that page from your server, then deliver it to your visitor.

Cloudflare can improve WordPress performance

You could create a Page Rule to “Cache Everything, but you don’t want your back-end pages cached, or the WP Admin Bar, or Editing links on your pages and posts because you were logged in when the Cloudflare cache refreshed your page. Your visitors will see what you see, and that’s not good.

How can you safely cache “dynamic” content?

If you’re on a Business Plan at Cloudflare…it means you spend a lot of money on your website. It also means you can enable a “Cache Everything with a Bypass on Cookie” setting that makes things run smoother, but any updates won’t show up. Fortunately, many WordPress caching plugins, such as WP Fastest Cache and the Cloudflare Plugin, connect to your Cloudflare account so it will clear the Cloudflare cache when you add or update a Page or Post. Great…if you have a Business or Enterprise plan.

Do You Want A Blazing Fast Site?

What if you’re on a Free or Pro plan?

You’ve still got options in Cloudflare to improve WordPress performance! All of these require some sort of plugin as mentioned above that will purge the cache when you make a content change.

Try Some Smart Page Rules

If your site looks the same for everybody, it’s easy to cache (eg. not WooCommerce or a member site). Some people go with just one Page Rule: Cache Everything for the entire site. But, as I mentioned, that causes problems with wp-login and wp-admin. So try some carefully crafted Page Rules in this order:

  1. Match example.com/wp-* and add settings: Always Online (Off), Security Level (High), and Disable Apps. This makes the backend run smoothly, yet retain the caching for static files (Images, CSS, JS).
  2. Match example.com/* and add settings: Cache Level (Cache Everything), Edge Cache TTL (One Month). This will cache pages for your visitors.
Be Careful!

When you Cache Everything, Cloudflare will cache the first view of that page. If you view your site while logged in, this may cause Edit links and the Admin toolbar to be cached for the next visitors. Use a separate browser that’s not logged in when viewing the site as you update it.

Cloudflare Workers to the Rescue!

Luckily, there are Cloudflare Workers that can do this included in the cost of Unlimited Workers ($5/month includes 10 million requests) that will stop those pages from being cached. Add this script called “seven-day-cache-everything” to your zone (domain) and set a route, such as example.com/*:

addEventListener('fetch', event => {
  event.respondWith(noCacheOnCookie(event.request))
})

async function noCacheOnCookie(request) {
  // Determine which group this request is in.
  const cookie = request.headers.get('Cookie')
  const cacheSeconds = 604800
  if (cookie 
    && (
      cookie.includes(`wordpress_logged`)
      || cookie.includes(`comment_`)
      || cookie.includes(`wordpress_sec`)
    )) {
    const bustedRequest = new Request(request, { cf: { cacheTtl: -1 } })
    const response = await fetch(bustedRequest)

    const newHeaders = new Headers(response.headers)
    newHeaders.append('wp-cache-busted', `true`)
    return new Response(response.body, {
      status: response.status,
      statusText: response.statusText,
      headers: newHeaders
    })
  } else {
    // Edge Cache for 7 days
    return fetch(new Request(request, { cf: { cacheTtl: cacheSeconds } }))
  }
}

Now add your routes:

  1. Route of example.com/* with the “seven-day-cache-everything” Worker.
  2. Route of example.com/wp-login.php with no assigned worker (“Workers are disabled on this route”).

Now all visitor requests to your site will put that response into the Cloudflare cache for seven days. If a site admin updates a page or post, the aforementioned plugins will clear that Cloudflare cache. If a logged in user visits that page, their WordPress cookie will send a Bypass Cache signal to Cloudflare and pull content directly from the website.

No Page Rules required! A Cache Everything page rule will override the Worker. I only have one Page Rule, and that is to quickly redirect requests to the ‘www’ subdomain to the main site.

Check Your Cache Settings!

In Cloudflare’s Caching section, make sure Caching Level is set to Standard, and Browser Cache Expiration is set to “Respect Existing Headers.”

When “Fast” is not Fast Enough

The thing about Cloudflare cache is it’s not a permanent CDN. If your site doesn’t get a lot of traffic, your cache will self-purge, and the next visitor gets a slower load because it goes all the way to the origin server. As if that’s not enough to slow things down, every edge server in every Cloudflare datacenter has its own cache. So if a user visits in Oregon through the Portland datacenter, only that cache applies. A user in Madrid gets a new cache at that datacenter. This generally works well enough if you get regular visitors from that area.

But you want to do more to improve your WordPress performance with Cloudflare. You want a full set high 90s (or more!) at fastorslow.com. Like this site’s homepage gets. All day, every day.

High WordPress Performance Numbers with Cloudflare at Fast or Slow
On a good day, it’s all 100s.

So I cheat by putting the entire page’s HTML in a Cloudflare Worker. Workers are stored locally at every edge server in the world, ready to Work at a moment’s notice. You’ll need shell access to your web server to do this. Let’s start with the top of the Worker. I save this file as head.txt. I have several HTTP headers I like my page to have. Delete any you want. I have a CSP place holder where I put my actual Content Security Policy.

async function handleRequest(request) {
  const init = {
    headers: {
      'content-type': 'text/html;charset=UTF-8',
      'cache-control': 'max-age=31536000',
      'X-Frame-Options': 'SAMEORIGIN',
      'Referrer-Policy': 'same-origin',
      'feature-policy': "fullscreen 'self'",
      'content-security-policy': "CSP",
      'X-XSS-Protection': '1; mode=block',
    },
  }
 return new Response(atob(someHTML), init);
}
addEventListener('fetch', event => {
  return event.respondWith(handleRequest(event.request))
})
const someHTML = `

And now to grab your page and create/update your Worker. I save this file as worker.sh and chmod it to 755. Make sure you change the following: HOST to the hostname (example.com) of your site; ACCOUNT_ID for your Cloudflare account; SCRIPT_NAME to what you want that script to be called; EMAIL to the email address associated with your Cloudflare account; and your GLOBAL_API_KEY.

#!/bin/bash
# Use 'curl' to get the page from your local host.
curl --resolve HOST:443:127.0.0.1 -o index.html https://HOST
# Convert the HTML to Base64 so it doesn't break the Worker
base64 --wrap=0 index.html > index.b64
# Add the end mark for the Worker file
echo "\`" >> index.b64
# Assemble your Worker
cat head.txt index.b64 > output.html
# Upload your Worker
curl -X PUT "https://api.cloudflare.com/client/v4/accounts/ACCOUNT_ID/workers/scripts/SCRIPT_NAME" \
     -H "X-Auth-Email: EMAIL" \
     -H "X-Auth-Key: GLOBAL_API_KEY" \
     -H "Content-Type: application/javascript" \
--data-binary "@output.html"
# Clean up
/bin/rm index.html index.b64 output.html

Now that you have a Worker, add it to your route: example.com/

I also run a periodic cronjob to keep the homepage up to date as often as I see fit. In this case, it’s three minutes past every hour.

3 * * * * /PATH/TO/worker.sh

The Finishing WordPress Performance Touch

With either of these approaches, you’ve improved WordPress performance with Cloudflare. The Worker-hosted page in the final example is as fast as it gets, but it only covers one page. To get that one last burst of speed, I recommend the Flying Pages plugin. It will preload internal pages while the visitor is looking at their current page. That’s even faster than the Worker page since it’s already preloaded locally in the visitor’s browser.

That should do it!

Subsequent visitors will now get a Cache HIT when they visit your page, and site admins or logged in users won’t spoil the cache with their custom views. Those logged in users will have a slightly slower page load time, but all your first time visitors and guests will get a speedy site. Or, in the case of my last example, everybody gets a super fast landing page experience without using the Cloudflare cache. Let me know if it’s not working as expected for you. If you have any questions on how to better improve

Are you running WooCommerce?

Then, for the most part, you’re out of luck. You can try the “Workers To The Rescue” tip, but be sure to add a || cookie.includes(woocommerce_) line. This will only speed the site up for visitors, but as soon as they shop, there’s no more caching. WooCommerce really needs managed WordPress hosting from a host that specializes in WooCommerce. You can go all out with Pantheon, or SiteDistrict that is reasonably priced for an excellent service.

Leave a Comment

Your email address will not be published. Required fields are marked *