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.

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 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.

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

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.

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

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 Disappearing WordPress Admin Toolbar

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 );

.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)

if ($http_cookie ~* "wordpress_logged_in") {
    #remove headers that prevent Cloudflare page cache
    more_clear_headers 'Pragma';
    more_clear_headers 'Expires';
    more_clear_headers 'Cache-Control';
    add_header Cache-Control "no-cache, no-store, max-age=0";
}

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