WordPress Caching: All You Need To Know

Over the last few years of supporting our products at Delicious Brains (notably WP Offload Media and most recently, SpinupWP), one thing has become clear:

Caching in WordPress is often misunderstood (and sites run slower than they should as a result).

In this post, I hope to demystify some of those misconceptions and provide a little clarity on the minefield that is WordPress caching. Hopefully, by the end of this post, you’ll have a better understanding of how the various layers of caching play a role in WordPress optimization.

What is Caching and Why is it Important?

A cache is a storage layer that stores temporary copies of data or files from one request, so that future requests can access that content faster. In WordPress, this can mean anything from caching an entire page, to caching the results of a database query.

Before we dive into the various caching mechanisms, it’s essential to understand the benefits of caching. Caching plays two major roles:

  1. It improves application performance. For WordPress sites, this means that your site loads faster and provides a better user experience.
  2. It increases application throughput. Meaning, your site can handle more traffic.

What’s more, caching can increase both application performance and throughput without increasing hosting costs. This is because you need far less system resources (CPU & memory) to host a site that has been correctly cached. Also, better performance can be beneficial for SEO, as page speed is one of the ranking factors that search engines will use. It really is a win-win strategy (when done correctly).

Caching Layers

WordPress is a database-driven CMS, meaning there are many moving parts when handling an incoming request. Out-of-the-box WordPress has to query the database and render the page before it can be sent to a user. This happens on every single incoming request, which is hugely inefficient if the page content hasn’t changed. A typical request will look something like this:

WordPress caching before

As a general rule, the more moving parts involved with handling a request, the longer a user has to wait for a response, and the more system resources are used. To combat this, caching is often performed in layers, with each layer sitting in front of a moving part. The three prominent layers in WordPress are often broken down to:

  1. Browser caching
  2. Page caching
  3. Object caching

WordPress caching after

Let’s dive into each of those layers. We’ll work from the outside in, which brings us to browser caching.

Browser Caching

Although browser caching doesn’t necessarily help with application response time or throughput (at least in the realms of WordPress), it is the most important layer when it comes to reducing the amount of data that must be sent from the server to the browser. This can make your site feel much more responsive because static assets such as CSS, JS, and images appear much quicker if they’re cached by the browser.

When it comes to understanding the browser cache, the network tab in your browser’s developer tools is your friend. Let’s take a look at the browser cache in action, by loading the SpinupWP site as an example. The first page load won’t have any assets cached by the browser. (I’ve turned on ‘Disable cache’ to spoof an initial page load).

Browser cache before

The metrics we’re interested in is the amount of data transferred and the total resources.

1.2 MB / 1.2 MB transferred
2.2 MB / 2.2 MB resources

First, be aware that each metric has two values (1.2 MB / 1.2 MB) because you can filter the results by resource type. This allows you to easily see which type of resources is taking up the majority of the bandwidth.

Browser cache filter

Second, the transferred value should always be lower than the total resources, even if this is the first time the page has been loaded by the browser (or ‘Disable cache’ is enabled), for two reasons:

  1. Assets which are browser cached aren’t transferred.
  2. Assets such as HTML, CSS, and JS should be compressed (using Gzip) by the server before being transferred to the browser. They’re then uncompressed by the browser before being displayed to the user.

Therefore, if browser caching is correctly configured, subsequent page loads should transfer less data. You can see this for yourself when we reload the page.

Browser cache after

Data transferred has dropped from 1.2 MB to 118 KB, which is a huge saving! You’ll also notice that the load metric has dropped from 893ms to 573ms.

You can configure browser caching behavior by the cache-control and expires headers which are added to the response of static assets by your server. The cache-control header will take precedence over the older expires header. But, both are usually applied to ensure compatibility with some legacy proxy services.

You can check an asset’s headers using the developer tools, or using the cURL command:

$ curl -I https://spinupwp.com/wp-content/themes/spinupwp/assets/images/video-screenshot.png
HTTP/2 200
server: nginx
date: Mon, 18 Jan 2021 21:10:09 GMT
content-type: image/png
content-length: 46753
last-modified: Tue, 13 Oct 2020 12:58:40 GMT
etag: "5f85a480-b6a1"
expires: Tue, 18 Jan 2022 21:10:09 GMT
cache-control: max-age=31536000

Typically, the cache-control header will have a value of max-age=<seconds> which will instruct the browser to cache the file for a maximum of <seconds> and prevent it from being re-downloaded on subsequent requests. In the case above, the file will be cached by the browser for up to 1 year.

Common Gotchas with Browser Caching

1. Cache invalidation is difficult. Once a browser has cached an asset using cache-control: max-age=<seconds> you can’t tell the browser to uncache it, at least not without changing the URL. This is why WordPress adds a version query string to enqueued assets. For example:

https://spinupwp.com/wp/wp-includes/css/dashicons.min.css?ver=5.2.2

2. Website speed test tools such as Pingdom will complain about short-lived expiration headers. Often, these warnings are triggered by external assets like Google Analytics. This is because external services have to use short expiration times to ensure their assets are up to date due to the difficulties with cache invalidation. With a far-future expiration time you would have to update your embed scripts every time Google Analytics changed their code. Ugh!

3. The cache-control and expires headers aren’t the only headers that the browser uses to determine whether a resource should be fetched from the server. For example, once the duration specified by cache-control: max-age=<seconds> has elapsed the etag header will be used. This contains a validation token (similar to an MD5 hash of the requested resource), which is compared to the token of the resource stored on the server. If the etag value is the same, the resource returns a “304 Not Modified” response. This tells the browser that the cached resource hasn’t changed and that it should renew the expiration time specified in cache-control: max-age=<seconds>. HTTP caching is an entire subject in itself, but you can read more about it here.

Page Caching

Page caching is going to give you the most bang for the buck when it comes to improving both application response time and throughput. A page cache essentially turns WordPress (a database-driven CMS) into a static HTML site by taking both PHP and MySQL out of the equation when it comes to handling a request.

To demonstrate how significant an impact page caching can have, I’m going to benchmark a clean installation of WordPress 5.6.2 using ApacheBench. In these tests I’m using SpinupWP to provision a 8 GB, 4 vCPUs DigitalOcean Droplet tuned for hosting WordPress sites. The uncached results are for WordPress with no caching configured (except PHP OPcache, using PHP 7.4 default values). The cached results have SpinupWP’s one-click page cache enabled.

Let’s start with requests per second (higher is better), which translates to:

It increases application throughput. Meaning, your site can handle more traffic.

Caching requests per second

That’s a 10x increase in requests per second, which is awesome! But, a high requests per second doesn’t mean much if those requests are slow to complete. That’s why it’s important to also measure the average response time (lower is better), which brings us to:

It improves application performance. For WordPress sites, this means that your site loads faster.

Caching average response time

Average response time is five times faster when the page cache is configured. This is pretty remarkable considering 10x the amount of requests per second are being handled.

Common Gotchas with Page Caching

1. Page caching is extremely difficult when sites display personalized content like e-commerce sites or are highly dynamic like forums. Often, the page cache will need to be completely disabled or specific pages like the /checkout will need to bypass the cache.

2. Cache invalidation can be tricky depending on how it’s implemented. Due to the dynamic nature of WordPress, it’s difficult to determine which pages should be purged from the cache when content is updated (especially when archives, pagination and widgets are involved). This is why purging the entire page cache is often the preferred approach when content has changed.

3. Using multiple page cache solutions won’t improve performance. In fact, having multiple page caching solutions is highly discouraged because it can cause cache incoherency (different versions of your site stored in each cache) and make cache invalidation difficult.

4. Page caching is usually disabled for logged in users, which is why object caching (below) is still advisable.

Object Caching

As mentioned previously, not all pages can be page cached. This is especially true of e-commerce and membership sites, which often display personalized content. It’s also true of the WordPress admin area. If such dynamic pages were page cached, users would see personalized content not relevant to them.

WordPress has object caching built-in, which allows data such as database queries to be stored in memory. This is how multiple calls to functions such as get_posts only results in a single database query. However, the object cache is non-persistent by default (meaning it doesn’t live beyond a request). Luckily, WordPress can be integrated with a persistent data store such as Redis, which is crucial for scaling dynamic pages. The object cache sits between PHP and the database which speeds up PHP execution time and reduces the load on the database by caching queries in memory. You can see the impact an object cache has by installing the Query Monitor plugin. Loading the WooCommerce cart page without object caching enabled results in 32 database queries:

Object cache disabled

With object caching enabled, this drops to 2 database queries and reduces page generation time from 0.085s to 0.053s:

Object cache enabled

These tests were performed using PHP 7.3, a clean install of WordPress 5.2.2, WooCommerce 3.6.4 and the Storefront theme.

Common Gotchas

1. Additional server software such as Redis or Memcached is required to make the object cache persistent. This should be taken care of if you’re using a good WordPress hosting provider, or you can install it yourself if you manage your own server.

2. The object cache doesn’t completely remove the reliance on the database, which is often the biggest bottleneck in WordPress. This is because the SQL queries to build the post index will always be executed as the results are not cached.

WordPress Caching Plugins

Popular caching plugins like WP Rocket, W3 Total Cache or WP Super Cache are a common way to add caching functionality to your site. However, you might not need to use a plugin at all. Keep in mind that caching plugins don’t always perform as well as server-based caching. A good WordPress hosting provider will take care of caching for you. Or you can follow our server setup guide to learn how to properly set it up on your own server.

What about Cloudflare?

If you are using Cloudflare, you may be wondering if the different caching layers are still necessary. To answer this, we need to understand that by default Cloudflare will only cache your static assets (CSS, JavaScript, images) on a CDN.

This means that when a visitor comes to your site, the cached version of those assets will be downloaded from the server on the CDN network that is closest to their location. This reduces the load time for downloading the site assets.

A CDN like Cloudflare can be used in addition to the other caching layers. Although page caching and object caching are still important to optimize your WordPress website. For more details, check out our article on why A CDN Isn’t a Silver Bullet for Performance.

Note that it is possible to configure page rules in Cloudflare for full page caching. However, the option to bypass the cache based on cookies is only available for Business and Enterprise plans. Being able to bypass the cache based on cookies is critical for a WordPress website in order to avoid serving cached responses to logged in users, or for other dynamic content like a shopping cart on an e-commerce website. Cloudflare does offer Automatic Platform Optimization for WordPress which can solve these problems at a cost of $5/month per domain for users that are on the free plan.

That’s All Folks

I’ve only scratched the surface of caching as it relates to WordPress. Hopefully, you have a better understanding of how the various caching layers work. If you would like to dive deeper, I recommend checking out HTTP Caching which gives more insight into browser caching. WordPress at Scale is also an excellent resource for learning more about WordPress specific performance.

Still not sure about WordPress caching? Ask away in the comments below.

Author

Ashley Rich

Ashley is a Laravel and Vue.js developer with a keen interest in tooling for WordPress hosting, server performance, and security. Before joining Delicious Brains, Ashley served in the Royal Air Force as an ICT Technician.

Start Your 7-Day Free Trial

Start your SpinupWP journey today and spin up your first server within minutes.

Start Your Free Trial

No credit card required

Subscribe to get the latest news, updates and optimizations in performance and security.

Thanks for subscribing 👍

To receive awesome stuff, you'll need to head to your inbox and click on the verification link we sent you.
Make sure to check your "spam" folder or your "promotions" tab (if you have Gmail).
If you're still having trouble, then messages us at sudo@spinupwp.com.

You are already logged in

It looks like you are already logged in to SpinupWP.

Please log out of this account to continue.

Registration Successful

Thanks for registering for a new
SpinupWP account.

Before getting started, could you verify your email address by clicking on the link we just emailed to you?

SpinupWP

Free Trial

Start Your 7-Day Free Trial

No credit card required. All features included.

By signing up to SpinupWP, you agree to our Terms and Conditions.
For privacy related information, view our Privacy Policy.