A look at the modern WordPress server stack

I gave a talk at WordCamp San Diego 2016 on the modern WordPress server stack. This is the companion article that I wrote for it. If you’re just looking for the slides, click here. It was also later republished on Smashing Magazine.

Do you remember when you could run a “fast” WordPress site with just an Apache server and PHP? Yeah, those were days! Things were a lot less complicated back then.

Now, everything has to load lightning fast! Visitors don’t have the same expectations about loading times as they used to. A slow site can have serious implications for you or your client.

As a consequence, the WordPress server stack has had to evolve over the years to keep up with this need for speed. As part of this evolution, it’s had to add a few gears to its engine. Some of the older gears have had to change as well.

The result is that today the WordPress server stack looks quite different from a few years ago. To better understand it, we’re going to explore this new stack in detail. You’ll see how the various pieces fit together to make your WordPress site fast.

Let’s start with an overview

Before we dive in, let’s zoom out and look at the big picture. What does this new WordPress server stack look like? Well, look no further!

Click for full size
Click for full size

The diagram above gives you a good overview of what the modern WordPress server stack looks like. At a high level, you can divide what’s going on in a modern WordPress server stack into three areas. These are:

  • The request-response cycle between your browser and WordPress.
  • WordPress (which is a script that the PHP runtime executes).
  • The query-result cycle between WordPress and the MySQL database.

The role of the modern WordPress server stack is to optimize these three areas. These optimizations are what makes everything load faster. And the best part is that there are several ways to do it. (Yay!)

In most cases, these optimizations revolve around installing new services on your server. Sometimes, these services will also need the help of a plugin to interact with WordPress. There will also be times where you can get away with just installing a plugin. You’ll see a lot of different options throughout this article.

The request-response cycle

Everything starts with your browser. Let’s say that you want to view the homepage of modern.wordpress-stack.org. Your browser will start by sending an HTTP request to the web server that hosts it. At the other end, the web server will take your request and turn it into an HTTP response.

This first response should always be the HTML content of the homepage of modern.wordpress-stack.org. (That’s unless there’s an error.) Except the job of your browser isn’t done then. No, that homepage still needs more files. The most common ones being CSS files, JavaScript files and images.

So the browser will send requests for those files. The web server will respond with the requested file. (Again, that’s as long as there are no errors.) This cycle will continue until the browser has enough information to render the homepage. The faster this cycle happens, the faster your website will appear to load.

Now, this is an obvious simplification. That said, that’s how things work for the majority of WordPress sites.

Optimizing the request-response cycle

Alright, so this brings us to the obvious question, “How do I make my web server do this cycle faster?” (This is a great question to ask btw!) This question is part of the reason why the modern WordPress server stack exists.

That’s because you can’t just make a web server faster. A web server is also a dispatcher. It can receive your request and just forward it to other services.

These other services are often the bottleneck to this faster request-response cycle. With WordPress, this bottleneck is PHP. Which is why optimizing the request-response cycle comes down to a two things. You want your web server to:

  • Receive as few requests as possible.
  • Forward as few requests to PHP as little as possible.

The modern WordPress server stack focuses on this last one. It wants to forward as few requests to PHP as possible. That’ll be the main optimization goal of the stack.

We focus on that goal because it can’t do much about the first one. It’s not something that the stack has direct impact on. It’s done with either web server configuration or modern development techniques.

Stack elements for the request-response cycle

So what are the stack elements that will help us reduce how many requests get forwarded to PHP? Well, there are two stack elements that will help us achieve that goal. They’re the web server and the HTTP cache.

Web server

We’ve been speaking quite a bit about web servers already. There are three big players in the web server space. Together, they make up more than 90% of the market share for web servers on the internet. These three web servers are:

That said, we’re only going to focus on Apache and nginx. While you can run WordPress on IIS, it’s not a common setup at all. That’s because it’s only available for Windows and most WordPress servers use Linux.

This leaves us with Apache and nginx. For all WordPress’s life, Apache has been the recommended web server. You had the LAMP stack (Linux-Apache-MySQL-PHP) and that’s what ran WordPress on both your computer and your server.

But behind the scenes, things were changing. There was a new player in town and his name was “nginx”. Automattic and WordPress.com have been using it since 2008. It’s the web server that runs the largest percentage of high traffic sites (a lot of which run WordPress). That’s why a lot of high-end hosting companies and top WordPress agencies use it as their web server.

It’s not that Apache is a bad web server. There are Apache experts that can make it run great under a lot of traffic. It just doesn’t do so as well out of the box or with the standard WordPress configuration.

Meanwhile, nginx’s sole purpose is to handle a lot of traffic. That’s why Igor Sysoev started the project while he worked at Rambler.

FastCGI

One of the reasons that nginx handles more traffic is that it uses FastCGI to communicate with PHP. What is FastCGI? It’s a protocol that lets PHP run as a service separate from the web server.

This isn’t something that Apache does by default. That means that each time your web server receives a request, it needs to start a PHP runtime process. And that’s even for images, JavaScript files and CSS files. This reduces the number of requests that your server can handle and how fast it can handle them.

Except this goes against one of the goals of the modern WordPress server stack that we saw earlier. It needs to keep the request-response cycle time as small as possible. Loading PHP for every request even if it doesn’t need it goes against this goal. So, even if you use Apache, you should look into FastCGI.

HTTP/2

HTTP/2 is another important web server feature that you should know about. It’s the next version of the HTTP protocol. This is the protocol that powers our entire request-response cycle.

Before the arrival of HTTP/2, your browser could only have six connections to the web server. And each connection could only handle one request at a time. So, in practice, our request-response cycle capped at 6 requests per cycle.

That was a real problem. Most websites have dozens of requests in their cycle. Developers and system administrators found clever ways to get around this limitation.

One of the better-known workarounds is the practice of combining CSS and JavaScript files. In the ideal scenario, this brings down your total requests for CSS and JavaScript files to two. You have one for the JavaScript file and one for the CSS file.

This isn’t necessary with HTTP/2. HTTP/2 allows an unlimited amount of requests per connection. This allows all our extra requests after the initial HTML response to happen at the same time.

This has huge performance implications. A lot of the work optimizing the server stack centers around the request-response cycle. By reducing the number of cycles to only a handful, HTTP/2 has done a tremendous amount of work for us.

HTTP cache

The most important part of modern WordPress server stack is the HTTP cache. In the WordPress world, we also call that page caching. The role of the HTTP cache is to cache responses to requests. What does that mean?

Well, let’s go back to our earlier example. Your browser sent a request for the homepage of modern.wordpress-stack.org. And the web server receives that request and forwards it to PHP.

The problem with this scenario is that the web server is dumb. It’ll always forward every request it receives to PHP. Regardless that most of these requests would generate the same response.

Which is exactly what happens when visitors aren’t logged in. To the web server, they’re all different requests, but it doesn’t care. It’ll forward them all to PHP which will generate the same response for each of them.

That’s terrible! As we saw earlier, PHP is the real bottleneck of our request-response cycle. Your browser can’t send its follow-up requests until it receives that initial homepage response. We can’t have the web server to forward everything to PHP by default.

And that’s where the HTTP cache comes in. It sits between the web server and PHP. Its job is to check every request that the web server receives and look for a cached response. If there isn’t any, it’ll forward the request to PHP and then cache the response that PHP generates.

This causes a drastic reduction the request-response cycle time. This makes your website load faster. It also lets your web server handle more simultaneous requests without blowing up.

The different flavors of HTTP cache

At this point, you should be wondering, “How can I get this baby on my server ASAP!?” The good news is that it’s quite easy to install an HTTP cache on a WordPress server. It’s the component with the widest range of options that you can use.

Install a page caching plugin

The easiest option is to install a page caching plugin. You have a few options to pick from:

All, but WP Rocket are available as free plugins in the plugin directory. So you can install one and test it out right away. That said, out of those four plugins, the best one is WP Rocket. While it’s a paid plugin, it also does a lot more than just create an HTTP cache. These other benefits magnify the work that the HTTP cache is doing.

How does a page caching plugin work?

All these plugins leverage a drop-in that WordPress has made available for caching. This caching drop-in lets them create an HTTP cache system inside of WordPress. The caching drop-in needs two things to work.

First, you need the advanced-cache.php drop-in file inside the /wp-content folder. That’s the actual file. But, unlike most WordPress drop-ins, this one doesn’t kick in by default. WordPress also needs the WP_CACHE constant to be true to load the drop-in. In most cases, you add it in wp-config.php.

Plugin HTTP Cache (loading)

The diagram above shows you what happens when you enable the drop-in with a caching plugin. WordPress loads the drop-in inside wp-settings.php during its loading process. This is early enough in the process that WordPress hasn’t done anything time-consuming yet.

The caching plugin will then check if it’s already cached the response to the request. If it has, it’ll return that cached response. If it hasn’t, it’ll turn on PHP output buffering and WordPress will continue loading.

Now, output buffering is an interesting system. What it does is capture all the output from a PHP script in a string variable until one of two things happen:

  • You can tell PHP to stop buffering its output using one of the built-in functions.
  • The PHP script finished and needs to return a response to the browser.

The caching plugin is counting on the latter scenario. It wants WordPress to do its thing and then cache the whole output before PHP sends it back to the browser. You can see the process in the diagram below.

Plugin HTTP Cache (shutdown)

Have the web server do it

The next option is adding an HTTP cache at the web server level. The way it works is that the web server will cache all responses to requests that get forwarded to PHP. This is a better solution to a caching plugin because it doesn’t need to touch PHP at all.

Web server HTTP cache

The diagram above gives you an overview of what’s going inside the web server. The web server receives a request and checks with the HTTP cache. If there’s a response cached already, the HTTP cache will send that back.

Otherwise, it’ll forward the request to the PHP module (usually FastCGI). It’ll pass with to WordPress so that it can generate a response. The HTTP cache module will then cache that response on the way back.

Both apache and nginx come with the ability to add an HTTP cache system. The nginx one is built-in. Meanwhile, the apache one is a separate module that you need to add to your apache installation.

That said, there isn’t a lot of information on how to use the apache module with PHP or WordPress. It might be because it’s not popular with the apache crowd or maybe there are some issues with it. There’s at least one long-standing issue with it that is still open.

Meanwhile, the nginx HTTP cache system is robust and well documented. You can use it as a normal HTTP cache or as a smaller yet effective micro cache. It’s just one more reason why nginx is the preferred web server nowadays.

Add Varnish to the stack

What is Varnish? It’s a dedicated HTTP cache server (or, as they like to call it, HTTP accelerator). Most high-traffic sites and premium hosting companies use it as their HTTP cache solution.

They use it because it’s powerful and offers the most flexibility. Varnish has its own configuration language called VCL. It lets you control every element of the caching process. Varnish also comes with a lot of tools for analyzing what the cache is doing and how it’s performing.

These are the major differences between it and just using the built-in web server HTTP cache. The built-in web server HTTP cache is super performant, but also pretty basic. You don’t have a lot of control outside a few configuration options.

That said, this power and flexibility comes at a price. Varnish is also the most complicated HTTP cache option. It does nothing, but cache HTTP responses. It doesn’t handle SSL termination which most WordPress site want (or should want). This means that our modern WordPress server stack is going to be more complex when we use it.

Click for full size
Click for full size

The diagram above illustrates this extra complexity. We now have two more components in our WordPress server stack: Varnish and a reverse proxy.

The reverse proxy is there to overcome the limitation that Varnish has with SSL. It sits in front of Varnish and decrypts the requests that our server receives. You can also call this type of reverse proxy an SSL termination proxy. This proxy then sends these decrypted requests to Varnish to process.

Once a request hits Varnish, the VCL configuration file(s) kick in. They’re Varnish’s brain. For example, they tell it how to:

  • Analyze, clean up and modify incoming requests.
  • Look for a cached response.
  • Analyze, clean up and modify returning responses from WordPress.
  • Cache these returning responses.
  • Handle a request to remove one or more responses from the cache.

This last one is especially important. Left to itself, Varnish has no way to know when WordPress wants to remove a page from the cache. So, by default, if you make changes to a post and update it, visitors will keep seeing the same cached page. Lucky for us, there’s a plugin that handles removing pages from the Varnish cache.

WordPress

Alright, so our request for the homepage of modern.wordpress-stack.org has hit WordPress. It went through the request-response cycle that we just covered. The HTTP cache did everything it could to find an HTTP response to send back.

But there was no cached HTTP response to send back to the browser. At that point, the HTTP cache had no other choice. It had to forward the HTTP request to WordPress.

So it’s all in WordPress’s hands now. WordPress has to turn our HTTP request into an HTTP response and send it back to the HTTP cache. As we saw earlier, this is the main bottleneck of our whole modern WordPress server stack.

The cause of this bottleneck is two-fold. WordPress has a lot PHP code to execute. This is time-consuming and the slower PHP is at doing it, the longer it takes.

The other bottleneck is the database queries that WordPress needs to do. Database queries are expensive operations. The more there are, the slower WordPress gets. This will be the focus of the last section on the query-result cycle.

Optimizing the PHP runtime

So let’s get back to PHP. At this moment, WordPress has a minimum requirement of PHP 5.2. This version of PHP is almost 10 years old! (The PHP team also stopped supporting it in 2011.)

The PHP team hasn’t been sitting idle all those years. There have been numerous performance improvements over those last 10 years. This is especially true over the last few years. Let’s look at what you can do to optimize it nowadays.

Use the latest version of PHP

The easiest thing you can do is upgrade your version of PHP. PHP 5.4, 5.5 and 5.6 all saw performance improvements compared to the previous version. The largest improvement was from 5.3 to 5.4. Switching to it increased the performance of WordPress by a decent amount.

Install opcode caching

Opcode caching is another way to speed up PHP. As a server-side scripting language, PHP has a big flaw. It needs to compile a PHP script each time it executes it.

The solution to that problem is to cache the compiled PHP code. That way PHP doesn’t have to compile it each time it executes it. This is the job of the opcode cache does.

Before PHP 5.5, PHP didn’t come bundled with an opcode cache. You had to install it yourself on your server. This is another reason why it’s better to use a more recent version of PHP.

Switch to a next generation compiler

The last thing that you can do is switch to one of the two next generation compilers. These are Facebook’s HHVM compiler or PHP 7 which is the latest version of PHP. (Why PHP 7? It’s a long story…)

Facebook and the PHP team built these two compilers from the ground up. They wanted to leverage more modern compilation strategies. HHVM uses just-in-time compilation. Meanwhile, PHP 7 uses ahead-of-time compilation. Both these compilers offer incredible performance improvements over good ol’ PHP 5.

HHVM was the first one to arrive on the scene a few years ago. A lot of the top-tier hosts have had a lot of success with it. They offer it as their primary PHP compiler.

That said, it’s worth stressing out that HHVM isn’t an official PHP compiler. It’s not 100% compatible with PHP. That’s because HHVM’s purpose isn’t just to support PHP. It’s also a compiler for Facebook’s Hack programming language.

Meanwhile, PHP 7 is an official PHP compiler. It hasn’t been around for a long time. The PHP team released it in December 2015. This hasn’t prevented some WordPress hosting companies from starting to support it already.

Regardless, the good news is that WordPress itself is 100% compatible with both compilers! But the bad news is that not all plugins or themes are. That’s because the minimum PHP version for WordPress is still PHP 5.2.

There’s nothing forcing authors to make their plugins or themes work with these compilers. This means that you can’t go all in with one of them. Your stack should always have a fallback to PHP 5.

Query-result cycle

So, at this point, the PHP runtime is going through all the WordPress PHP files and executing them. The problem is that these WordPress PHP files don’t contain any data. They only contain the WordPress code.

The problem is that WordPress stores all its data in a MySQL database. So, to get to it, the PHP runtime needs to query that database. The MySQL server returns the result of that query. The PHP runtime can then continue to execute the WordPress PHP files. Well, that’s until it needs data again.

This back and forth can happen a few dozen times to a few hundred times. (You might want to have a talk with your developer(s) if it’s the latter!) That’s why it’s a major bottleneck.

Optimizing the query-result cycle

The optimization goal here is to speed up the execution time of WordPress files by PHP. This is where database queries are problematic. They tend to take more time than just running plain PHP code. (Well, that’s unless your code is doing something outrageous!)

Now, the obvious way to fix this problem is to reduce the number of queries that WordPress needs to do. And that’s always something that is worthwhile to do! But it’s not something the modern WordPress server stack can help with.

We might not be able to reduce the number of queries that WordPress does, but we aren’t out of options either. There are still two ways that the stack can help us optimize the query-result cycle. It can reduce the number of queries make it to the database in the first place. And, if these queries make it to the database, it can also decrease the time that it takes to run them.

These two options are both looking to do the same thing. They want to make PHP wait as little as possible for results from the database. This, in turn, will make WordPress itself faster.

Stack elements for the query-result cycle

Let’s look at the different stack elements involved in the query-result cycle. This part of the stack is less complex. That said, it still involves more than one component. These are the MySQL database server and the object cache.

MySQL database server

A few years ago, a MySQL database server would mean the same thing to everyone. It was a server with MySQL server installed. But things have changed a lot in the recent years.

Different groups weren’t happy with how Oracle was managing the MySQL project. So each group forked it and created their own version that you could “drop-in” instead. The result is that there are now several MySQL database servers.

The new “official” MySQL server is the MariaDB server. It’s the community-developed version of MySQL server. They plan to maintain full compatibility with the MySQL server project.

Another popular alternative to MySQL is the Percona server. Unlike MariaDB, Percona is more of a branch of MySQL. They aren’t against the MySQL project itself. They just wanted to focus on improving MySQL’s performance. The MariaDB team later merged some of these performance improvements into the MariaDB project.

So, at the end of the day, you can pick the one that you prefer. There’s no difference between the performance of a Percona server and a MariaDB server. (For most of us anyhow.) They both perform better than MySQL. That said, Percona maintains closer compatibility with the Oracle project.

Storage engines

What has an impact on performance is the storage engine that the WordPress database uses. The storage engine controls how the database server will manage the data that it stores. You can also set a different storage engine per database table. You don’t have to use the same one for the entire database.

Your database server has several storage engines. We’re not going to look at all of them. In fact, there are only two that interest us. These two are InnoDB and MyISAM.

By default, WordPress will use the default MySQL database engine. Before MySQL 5.5, that engine was MyISAM. And, if you run a small WordPress site, it’s fine to use MyISAM. That said, MyISAM runs into performance issues once your site starts to grow in size. At that point, InnoDB becomes the only choice as a database engine.

The only issue with InnoDB is that it requires some tuning to perform at its best. If you’re running a large database server, you might need to adjust things. Lucky for us, there’s a tool to help with that.

MySQL tuner is a small script that’ll analyze your database server. It’ll generate a report and give you tuning recommendations. You can then chose to use them or not.

Object cache

The brunt of the work for optimizing the query-result cycle lies with the object-cache. The job of object cache is to store data that was time-consuming to get or generate. As you might guess, database queries are a perfect fit to this definition.

WordPress uses the object cache a lot. For example, let’s say that you use get_option to get an option from the database. WordPress will only query the database for that option once. It won’t query it again the next time someone needs it.

Instead, WordPress will fetch the query result from the object cache. This is a proactive step that WordPress does to reduce the number of database queries that it needs to do. But it isn’t a full-proof solution.

While WordPress will do its best to leverage the object cache, a plugin or theme doesn’t have to. If they do a lot of database queries and don’t cache the results, there’s nothing that the stack can do about it. You just

That said, in those cases, most of the database queries will come from WordPress itself. That means that you’ll get great mileage out of WordPress’s built-in use of the object cache. That’s why it’s an important element of the modern WordPress server stack.

Use a persistent object cache

Now, a problem with the object cache is that it doesn’t persist the data it stores by default. It just stores data in memory while PHP is executing all the WordPress files. But, once the PHP process terminates, all the data that it stored in memory gets cleared.

This isn’t ideal at all. Your object cache can stay valid for a long time. So you don’t want to limit the object cache to a single request. The solution is to use a persistent object cache.

A persistent object cache often comes in the form of a plugin. That plugin makes use of the object-cache.php drop-in to do its job. This drop-in lets plugin authors change the default behaviour of the object cache.

These plugins will then connect the object cache to a persistent data store. They do that by replacing the fetch and save functionality of the default object cache. Instead of saving and fetching data to memory, the object cache will do it from that store.

Persistent object cache plugins

Nowadays, there are two popular data store options for persistent object caching. They are:

Both these data stores use RAM for storage. This makes them lightning fast. In fact, they have a comparable performance to the default object cache.

The only issue is that they don’t come pre-installed on a server. And neither does their PHP extension (optional with Redis). You need to install those before you can use the corresponding WordPress plugin.

That said, which one should you install? In practice, there isn’t much of a difference between the two for object caching. In the past, the popular option was memcached.

This changed over the last few years. Redis has added a lot of features that make it standout versus memcached. Because of this, it’s now the go-to option for object caching.

Getting your own modern WordPress server

So how can you get your own server that? The obvious answer is to get one from one of the top-tier WordPress hosting companies. They want to stay at the forefront of the WordPress hosting business. This motivates them to use the latest breakthroughs and technologies.

But what if you want one without breaking the bank and paying as much as these hosting companies charge? There are a couple of tools available to anyone who’d rather do this themselves and pay less on hosting. Let’s take a look at them.

DebOps for WordPress

DebOps for WordPress is a tool that I built to help make anyone build a modern WordPress server. Its mission is to make this modern WordPress server stack available to anyone in the community. That’s why I’m trying to make it as easy to use as possible. You also don’t need any system administration knowledge to use it.

DebOps for WordPress configures a server with:

  • HHVM (until PHP 7 makes it into an official Linux repo)
  • MariaDB
  • nginx
  • Redis
  • Varnish

The tool does more than just configure your server with the latest technologies. It also takes care of securing your server for you. This is something that people often overlook when they manage their own server.

EasyEngine

EasyEngine is a command-line tool designed to help you setup a WordPress site on a server. The great thing with EasyEngine is its flexibility. You can use it setup almost any combination of server technologies that you’ve seen so far.

For example, it lets you setup a server with either HHVM or PHP 7. It also lets you chose between Memcached or Redis for your persistent data store. It also lets you install admin tools like phpMyAdmin.

It also offers a large amount of options available when it creates a WordPress site. You can tell it to setup a site with an HTTP cache using a plugin or nginx. All this flexibility is why EasyEngine is such a popular tool.

Trellis

Trellis is a tool developed by the Roots team. Like DebOps, it configures your server around a specific set of server technologies. In the case of Trellis, your server comes with:

  • MariaDB
  • Memcached
  • nginx
  • nginx HTTP cache (optional)
  • PHP 7

One thing that you should know about Trellis is its relationship with Bedrock. This is another tool that the Roots team built. It’s a boilerplate for structuring a WordPress site around Twelve-Factor app principles.

The Roots team created Trellis to configure a server that uses Bedrock-structured WordPress site(s). You can’t use it with a normal WordPress installation. So that’s something to keep in mind if you want to use Trellis.

Times have changed

As you can see, there are a lot more moving parts to a WordPress server today! But this doesn’t need to be a cause for despair. In fact, it’s not as bad as it looks because you don’t always need to use all these different parts.

That’s why so much of this article discussed how these parts work together. The point of it is to empower you to make your own decisions. Use this knowledge to decide which parts you need to use and when. And this way you too will have a fast WordPress site.

Slides

  • Really good article, Carl. Will be digging through this for a pro bono project I’m currently working on. Thanks so much.

  • Thanks for the great summary Carl. A quick correction: “WordPress in fact uses whatever MySQL’s default table handler is, and from 5.5 onwards, that’s InnoDB.”

    • Oh, I couldn’t find that! I’ll update the article.

  • I’ve gotten a few comments about that. I did some research this morning and added some details on Percona server.

  • Rare

    Should have Litespeed and Lscache listed as a Webserver option. That coupled with the new Litespeed cache plugin gives better performance than other stacks and caching from my testing. Check here http://roastahost.com

    I haven’t had a chance to test with Redis yet but that should be fun, thanks for the article it is a good one!

  • @carlalexander:disqus, fantastic article and talk! Thanks! Do you have any thoughts or experience in using Google’s Pagespeed module? I have used very similar stacks to the one you laid out here and have done some basic testing with and without compiling nginx with the pagespeed module, but I would love to hear your thoughts on that. Thanks!

    • Thanks Collin! I’ll be honest I haven’t tried ngx_pagespeed. I just did a quick research and it seems that ngx_pagespeed works under the assumption that the generated HTML isn’t going to be cached.
      https://github.com/pagespeed/ngx_pagespeed/issues/177#issuecomment-14816619

      So I’m not sure how useful it’ll be outside a setup that uses a caching plugin. That’s because whatever HTML the caching plugin generates will go through ngx_pagespeed.

      That said, this comment was written in 2013. It’s also possible things have changed since then.