Cloudflare Cache WordPress Posts and Pages Guide

Cloudflare helps speed up WordPress and WooCommerce sites all around the world. Powered by more than 100 datacenters globally, Cloudflare’s CDN and security is a great addition to any web site. By default Cloudflare will cache a lot of your static assets which help decrease load times. If you want to speed your site up even further then experimenting with Cloudflare page caching for WordPress is worth exploring. This is especially useful for people who find their TTFB (Time to First Byte) has slowed down after using Cloudflare.

The goal of this tutorial is to show you how to reduce your server response time (TTFB) around the world using Cloudflare’s datacenters on the Free and Pro plan.

Business and Enterprise plans with Cloudflare let you bypass cache on cookies which makes things a bit easier, but these plans start at $200 per month.

Note: this should be considered experimental and tested on a staging/development site.

Random weird behavior can happen with edge caching, be careful!

Cloudflare Cache WordPress Posts and Pages Guide

Overview

  • Benchmarks with and without page caching
  • Configure Cloudflare page cache
  • Testing Cloudflare page cache
  • Troubleshooting Cloudflare page cache

Cloudflare Page Cache for WordPress Benchmarks

This site is running on a Vultr (was Digital Ocean) VPS in Europe so I chose to test from Australia on pingdom.

Note that these benchmarks are without a warmed up Cloudflare CDN. The primary goal of these benchmarks is to show the server response (Time to First Byte – TTFB) benefits

Without Cloudflare Page Cache

The first test took 2.35s – note that the Cloudflare CDN was not warmed up for this test!

cloudflare-cache-page-wordpress-before

The Time to first Byte (TTFB) before Page caching with Cloudflare was 657 ms

cloudflare-page-caching-wordpress-ttfb-without

After Cloudflare Page Caching

After enabling Cloudflare page caching the site took 424 ms to load. At this point the Australian datacenter has my static assets and HTML.

cloudflare-cache-page-wordpress-after

The Time to first Byte (TTFB) after Page caching with Cloudflare went down to 43 ms

cloudflare-page-caching-wordpress-ttfb-with

This demonstrates the benefits of page caching with Cloudflare, going from a TTFB of 657 ms to 43 ms is a massive improvement of 15x faster.

Note: it is very likely with a TTFB that low that the pingdom server is in the same datacenter as Cloudflare’s servers.

Configure Cloudflare Page Cache

Sign in to Cloudflare go to Page Rules in the top navigation.

Create a Page Rule where If the URL matches is your full domain name with a /* at the end like https://guides.wp-bullet.com/*

The setting is Cache Level and should be set to Cache Everything

Click Save and Deploy

cloudflare-cache-wordpess-posts-pages-page-rule

Create another page rule for https://guides.wp-bullet.com/wp-admin/* and set Cache Level to Bypass.

Click Save and Deploy

Under the top level menu Cache, set Browser Cache Expiration to Respect Existing Headers this will tell Cloudflare to respect the max-age of our Cache-Control header.

I would highly recommend reading more about how Cloudflare works with origin cache control.

Once enabled you can scale with Cloudflare very beautifully!

Testing Cloudflare Page Cache

Using cURL or even pingdom can show you if the CF-Cache-Status header is set. The first time the page is accessed it will be a MISS.

Testing with cURL

To send a cURL HEAD request use this command from a linux command line.

curl -I https://guides.wp-bullet.com/

Notice the CF-Cache-Status header in the output

HTTP/1.1 200 OK
Date: Tue, 15 Nov 2016 09:32:01 GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
Set-Cookie: __cfduid=d7f5d39ef87df866a7ed2fd85399064cb1479202321; expires=Wed, 15-Nov-17 09:32:01 GMT; path=/; domain=.wp-bullet.com; HttpOnly
Link: <https://guides.wp-bullet.com/wp-json/>; rel="https://api.w.org/"
Vary: Accept-Encoding
X-Varnish: 558612 3
Via: 1.1 varnish-v4
Access-Control-Allow-Origin: http://guides.wp-bullet.com
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
X-Cache: HIT
CF-Cache-Status: MISS
Expires: Fri, 16 Dec 2016 09:32:01 GMT
Cache-Control: public, max-age=2678400
Server: cloudflare-nginx
CF-RAY: 3021b19009fe63c1-FRA

If you repeat the pingdom test or use cURL you will see the CF-Cache-Status header change to HIT if everything is working correctly.

HTTP/1.1 200 OK
Date: Tue, 15 Nov 2016 09:32:05 GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
Set-Cookie: __cfduid=d17975371e9adf5c8d82848b58590d96d1479202325; expires=Wed, 15-Nov-17 09:32:05 GMT; path=/; domain=.wp-bullet.com; HttpOnly
Link: <https://guides.wp-bullet.com/wp-json/>; rel="https://api.w.org/"
Vary: Accept-Encoding
X-Varnish: 558612 3
Via: 1.1 varnish-v4
Access-Control-Allow-Origin: http://guides.wp-bullet.com
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
X-Cache: HIT
CF-Cache-Status: HIT
Expires: Fri, 16 Dec 2016 09:32:05 GMT
Cache-Control: public, max-age=2678400
Server: cloudflare-nginx
CF-RAY: 3021b1a37dea6499-FRA

Testing with pingdom

You can also examine the response headers using pingdom. Find the first response and click the arrow in the top right.

Look for the cf-cache-status header and it should show HIT.

If you are still seeing MISS after repeated attempts, then you may need to remove some headers which prevent caching and add some cache headers. This requires a custom plugin like the one shown below which I used Pluginception to create or modifying .htaccess, both methods are shown below.

Troubleshooting Cloudflare Page Cache

Essentially if you have Pragma: no-cache or Expires set in the past then Cloudflare will not cache those pages.

You must have a Cache-Control header set that Cloudflare’s servers will respect.

If you are using JetPack login then you should add a Cloudflare page rule to bypass caching for domain.com/wp-login.php* (thanks Eric).

Custom Plugin for Setting Cache-Control Headers

Using Pluginception you can create a custom plugin like the one below which removes the Pragma, Expires and Cache-control headers.

It then adds the Cache-control header, here it is 3600 seconds which is 1 hour.

//Disallow direct access
defined( 'ABSPATH' ) or die( 'No Access' );

add_action( 'send_headers', 'remove_headers', 10000 );
function remove_headers() {
    if ( is_admin() ) { 
        return;
    }
    header_remove('Pragma');
    header_remove('Expires');
    header_remove('Cache-Control');
    header('Cache-Control: max-age=3600');
}

If the above doesn’t work you can try with this custom plugin instead

//Disallow direct access
defined( 'ABSPATH' ) or die( 'No Access' );

add_filter( 'wp_headers', 'remove_headers', 10000 );
function remove_headers( $headers ) {
    unset($headers['Pragma']);
    unset($headers['Expires']);
    unset($headers['Cache-Control']);
    $headers['Cache-Control'] = 'max-age=3600';
    return $headers;
}

Activate the plugin and re-test.

Fix Previews Showing as Cached

The typical URL for preview pages in WordPress looks like this https://guides.wp-bullet.com/?p=6336&preview=true and in this format it will be cached by Cloudflare since there is no exception set for it. You have two choices to fix this undesirable behavior:

Option 1 – Create a page rule to bypass the cache for URLs containing https://guides.wp-bullet.com/*&preview=true

Option 2 – Create a mini plugin using Codesnippets or something similar to dynamically add a unique query string to preview pages so the preview URL becomes https://guides.wp-bullet.com/?p=6336&preview=true&no-cache=1549977754. The snippet below works well on this site:

//add random timestamp to preview links
function add_no_cache_timestamp($url) {
   $slug = basename(get_permalink());
   $mydomain = 'https://guides.wp-bullet.com';
   $mynewpurl = "$mydomain$slug&preview=true&no-cache=" . time();
   return "$mynewpurl";
}

add_filter( 'preview_post_link', 'add_no_cache_timestamp' );
add_filter( 'preview_page_link', 'add_no_cache_timestamp' );

Fix Disappearing WordPress Admin Toolbar

In general you should not be viewing your published posts and pages while logged in as this will cache the admin toolbar view!

If you find the admin toolbar has disappeared you can bring it back with this snippet in a custom plugin.

//Disallow direct access
defined( 'ABSPATH' ) or die( 'No Access' );

function wpb_bring_admin_back() {
    if ( is_user_logged_in() ) {
        add_filter( 'show_admin_bar' , '__return_true' );
        show_admin_bar( true );
    }
}

add_action( 'wp_loaded', 'wpb_bring_admin_back', 10000 );

If you only want the admin toolbar to work for admins this should do the trick.

//Disallow direct access
defined( 'ABSPATH' ) or die( 'No Access' );

function wpb_bring_admin_back() {
    if ( is_user_logged_in() && current_user_can('manage_options') ) {
        add_filter( 'show_admin_bar' , '__return_true' );
        show_admin_bar( true );
    }
}

add_action( 'wp_loaded', 'wpb_bring_admin_back', 10000 );

Hopefully this helps display your admin toolbar only where it should be shown!

.htaccess Method

Open up your  .htaccess file and add this snippet at the bottom


# Begin Cloudflare headers for page cache
<IfModule mod_rewrite.c>
RewriteCond %{REQUEST_URI} !/wp-admin [NC]
RewriteCond %{HTTP_COOKIE} !"wordpress_logged_in"
RewriteRule ^.*$ - [ENV=LONGCACHE:true]
<IfModule mod_headers.c>
Header unset Pragma env=LONGCACHE
Header unset Expires env=LONGCACHE
Header set Cache-Control "max-age=600" env=LONGCACHE
</IfModule>
</IfModule>
# End Cloudflare headers for page cache

Save the .htaccess file and re-test Cloudflare page caching.

nginx Cache Headers

To remove headers with nginx you need the more_headers module which is included with the nginx-extras package on Debian and Ubuntu.

In your nginx virtual host add this if statement location block (untested)

location ~* "(!?.*wp-(admin|login)\.php.*)" {
    #remove headers that prevent Cloudflare page cache
    more_clear_headers 'Pragma';
    more_clear_headers 'Expires';
    more_clear_headers 'Cache-Control';
    add_header Cache-Control "max-age=3600";
}

Reload nginx and test if the page is cached by Cloudflare now.

Don’t forget to install the WordPress Cloudflare plugin so the page cache will be automatically cleared when you update your site.

Sources

Cloudflare Custom Caching Options Explained
Cloudflare Cache Everything
Remove HTTP Headers in .htaccess
Conditionally Set Cache Headers in Apache
Set headers Conditionally Stackoverflow
Conditionally Set Headers in Apache
nginx Clear Headers
Add nginx Headers
Cache Control Headers