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!
The Time to first Byte (TTFB) before Page caching with Cloudflare was 657 ms
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.
The Time to first Byte (TTFB) after Page caching with Cloudflare went down to 43 ms
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
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