Beating Some Performance Into WordPress

I’m a compulsive optimizer, which makes it unfortunate that I have so many WordPress sites (for my businesses, for my interests, for my friends), because WordPress is slow out of the box. So now what?

Faster Alternatives to WordPress

In the category of, “Now you tell me,” it turns out that a competing open-source blogging/content-management system, Drupal, has just undergone big changes to make it fast by default. You can read an interesting article on Performance Planet about this.

Speeding up WordPress With Sneaky Caching

You can take as deep a dive into WordPress as you like, but it boils down to something very simple:

WordPress is slow when it has to render a blog post or other page from scratch, so don’t do that more than once. That’s what caching is for.

Sadly, WordPress makes the assumption that pages should be as non-cacheable as possible, and that your readers like nothing more than to wait a couple of extra seconds while a blog post that hasn’t changed is recompiled for no reason.

Fixing this can be done in different ways: pick your poison. I did a three-step fix:

Step 1. Install a WordPress Caching Plug-in.

I like WP Super Cache. This is not absolutely necessary, but it makes the other steps easier WP Super Cache has an unusually simple user interface and is easy to get running. (Install it and enable it with default settings.  That’s enough to start with.)

Step 2. Edit your Apache configuration to undo WordPress’ anti-caching decisions.

WP Super Cache creates a distinctive Cache-Control header when it serves a cached file, and WordPress creates an equally distinctive Cache-Control header for other WordPress files. This allows me to fix the caching in four configuration lines (which can go into your .htaccess file or a customer configuration file. And, unlike editing the relevant WordPress files themselves, which I’d have to do once per blog, I can make this Apache fix just once per server.

First, let’s extend WP Super Cache’s paltry three-second expiration time to something three orders of magnitude longer. I chose 3333 seconds (55.55 minutes) to make the results more distinctive-looking in the logs:

Header always edit Cache-Control "max-age=3, must-revalidate" "max-age=3333"

WP Super Cache is declaring, in the Cache-Control: header, that one of its cached pages is good for only three seconds. During that time, it can reuse the page without contacting the server to see if there’s an update. After that, the brower must revalidate its copy by contacting the server. If the file hasn’t changed, the browser can reuse it. Otherwise, it fetches a fresh copy.

I’ve replaced this with a simple, “max-age=3333.” (For debugging purposes, I use distinctive numbers.) This means the page is good for 55.55 minutes, and once you have a copy, you don’t need to check with the server until this time has elapsed.

Since my Web pages are rarely updated, and don’t announce things that are particularly time-critical, it wouldn’t really matter if the max-age were extended to a day, or a week, or even longer.

As the author, it matters to me, though. I don’t want to have to remember to hit the refresh button on my browser every time I update a page, because sometimes I’ll get forget, and then I’ll wonder where my edits went.

But WP Super Cache’s (default) options is to not deliver cached pages to logged in users, but create a fresh rendition each time, so that’s taken care of.

Now let’s do something about any WordPress pages that WP Super Cache doesn’t handle.  This takes three lines:

Header always edit Cache-Control "no-store, no-cache, must-revalidate, post-check=0, pre-check=0" "max-age=2"
Header always unset Expires
Header always unset Pragma

Once again, I’ve thrown away all of WordPress’s fussy cache-busting clauses in the Cache-Control: header, and replaced them with a simple “max-age=2.” I also throw away the Expires: and Pragma: headers, which do nothing but duplicate what’s in the Cache-Control: header anyway.

This is a minimalist approach. I actually had something a bit fancier in mind, but Apache thwarted me with mysterious refusals to do what I wanted. Whether it was an issue of syntax, scope, or Apache bugs was unclear, so I’ve shelved that for now.

Step 3. Install a Squid “Reverse Proxy” Cache

This last step is a topic for a future post, but placing a Squid proxy cache on my Web server allows much higher resilience and performance, now that I’ve forced the WordPress traffic to be at least minimally cacheable.


These changes allowed me to greatly speed up my Web sites, which are doing quite well on speed tests in spite of my using a  no-frills, $7 per month virtual private server as my main Web host.

In particular, the “time to first byte” is quite good. (Annoyingly, my test page got a lousy score for caching static content. This was almost entirely due to resources loaded from external sites. My stuff was fast!)

Effect of caching on WordPress

I have some other tricks up my sleeve as well, but they’ll need to wait for another day.