The right server setup can take a WordPress application to a different level.
The traditional stack for a WordPress site includes Apache, MySQL, and PHP. While this may work for most sites, we've found other options or improvements that help us get the best performance we can.
A typical request to an untuned server with no caching can take time. As more traffic arrives, backend processes can easily be overwhelmed.
For example, here are some example benchmark tests against a WordPress application with no tuning or caching of any kind vs. the same application on a finely tuned server.
A very unscientific look at requests to a WordPress application with no tuning vs. an application that has been performance tuned. X-axis represents various intervals during the duration of the test.
So what does a "tuned and optimized" setup consist of? Below are some of our recommendations, and a link to download a set of Ansible playbooks for quickly building a tuned and optimized stack for WordPress.
Before diving into various performance improvements, it's important to understand how a typical request to your WordPress application operates.
Almost every request will get information from the database and each request to the database takes time, which can slow down load time. Requests also require the PHP interpreting of a backend server (such as Apache or PHP-FPM). This is where caching comes in.
The two main types of caching, as it relates to a WordPress application, are object level caching and ouptut level caching.
Object level caching can hold results from WordPress database queries or expensive calls to external APIs in memcached and output level caching holds the rendered output of the page for faster subsequent serving. Object level caching can consist of a memory store that is first checked for specific data. If the data is present, it is returned to the function requesting it. If the data is not present, it could result in a database call or external HTTP query.
Output level caching will return rendered page content to each visitor. Under certain circumstances, you wouldn't want to serve cached output for things like HTTP POST requests, or when the visitor has certain WordPress cookies.
Afterburner request/response life cycle:
NGINX is a high-performance HTTP server. It is the first software layer a client request interacts with. It excels at delivering static assets and acting as a reverse proxy.
NGINX, however, isn't capable of interpreting PHP code directly. Instead NGINX can proxy a request for PHP pages to a backend. The backend server, in most cases, consists of Apache or PHP-FPM.
NGINX can also act as a caching proxy. It can forward needed requests to backend servers, but also hold the responses in cache for efficient delivery on subsequent requests.
Controlling HTTP headers is also important. For example, a typical request for a static asset may return the following headers:
Last-Modified: Mon, 08 Jul 2013 12:07:51 GMT Cache-Control: max-age=2764800, must-revalidate
Once a browser has received an asset from the server with these headers, upon subsequent requests the browser will send the following header:
If-Modified-Since: Mon, 08 Jul 2013 12:07:51 GMT
If the asset on the server has been modified, it's modification date/time is now greater than the date/time provided in the
If-Modified-Since header. As a result NGINX will deliver the updated static asset, as requested.
If the static asset hasn't been modified, the server will send back a
304 Not Modified header instructing the browser "nothing has changed here, use the cached copy you have".
This keeps bandwidth usage low by only sending assets that the browser doesn't already have a copy of, since it's cheaper to send only the bytes to indicate a
304 Not Modified response versus re-sending the entire static asset.
User-Agentstring (normal, mobile, mobile-maybe) - this ensures content is cached differently for mobile vs. desktop data
Expiresand also add headers to indicate cache status (whether a request was a cache 'HIT' or cache 'MISS' via an
PHP-FPM (FastCGI Process Manager) is an alternative PHP FastCGI implementation with some additional features (mostly) useful for heavy-loaded sites.
PHP-FPM boasts several improvements over Apache including, but not limited to:
It's also encouraged to use opcode caching. Opcode caching works by storing the compiled opcode of PHP files in memory, which speeds up future requests and reduces the amount of reads from disk (which can slow down performance).
open_basedirsetting on a per PHP-FPM pool to confine applications to specific directories.
request_slowlog_timeoutto log requests that take too much PHP-FPM time.
ZendOptimizerPlusopcode caching (available on GitHub or through PECL).
ZendOptimizerPlusis included and compiled to a shared object in PHP 5.5.
The critical component of a dynamic application such as WordPress is the database. WordPress uses a MySQL database to store user data, post data, site options and more.
A great way to get started with MySQL configurations is Percona's free tool called the MySQL Configuration Wizard. It asks a few questions about your needs to build a recommended MySQL configuration.
Once your MySQL server is setup, take advantage of the free tool, MySQL Tuner by Major Hayden.
MySQLTuner is a script written in Perl that allows you to review a MySQL installation quickly and make adjustments to increase performance and stability. The current configuration variables and status data is retrieved and presented in a brief format along with some basic performance suggestions.
Inevitably, once your WordPress site is up and running, you'll come across one or two MySQL queries that can take more than a few seconds to process. An important part of maintaining your stack is to audit the MySQL slow query log.
Percona offers another tool to help you audit the logs. pt-query-digest is included with Percona MySQL and breaks down performance of individual slow queries, their average times, how many slow queries occurred, and more.
mysqltuner. It analyzes MySQL's usage and can report useful recommendations on specific MySQL settings to tweak in your environment.
Memcached is an in-memory key-value store process that can run on your server alongside your WordPress application, or on separate servers.
Object level caching, provided through the
object-cache.php drop-in plugin (available here) can significantly reduce the amount of data PHP-FPM needs to query the MySQL database to retrieve.
If the requested object data exists in Memcached, it's returned to PHP-FPM for continued processing of a dynamic page.
If the requested object data doesn't exist in Memcached, Memcached will instruct PHP-FPM that the value doesn't exist, and it should retrieve this data from the MySQL database instead.
Object level caching allows you to save parts of your page to memory (in memcached). This is helpful when, for example, a widget in a sidebar must perform a resource taxing database query. The output of the widget can be cached as an object with instructions on how long it is valid for, saving subsequent requests from having to perform the same resource taxing database query.
Using the memcached object level caching doesn't just apply to MySQL database queries! You can also object cache output for expensive data processing, or the results of a query to an external API or resource.
Afterburner uses Ansible for configuration management. It supports tuning of a single server to handle traffic for a WordPress application, or splitting off web and databse resources across multiple servers.
Check out afterburner-ansible on Github for a completely setup stack.
No prior knowledge of Ansible is needed. Download the repo, run the install script, update a few settings for your environment, and run the Ansible Playbook (don't worry, we'll walk you through it). You can have an optimized and tuned WordPress application up and running in just a few minutes with the recommendations made here.