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.

benchmarks - no tuning

benchmarks - tuned and optimized
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.

Request/Response Life-cycle

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:

  • A browser requests your web site
  • NGINX, listening on port 80 answers the request:
    • If the request is for a static asset, NGINX serves the asset
    • If the request is for a dynamic page, NGINX:
      • Checks if the request can be output cached
      • If it can be output cached, and output exists in the cache:
        • Return the cached output without passing to a backend
      • If it can be output cached, and no output exists in the cache:
        • Pass the request to a PHP-FPM backend
  • PHP-FPM backend interprets PHP application code:
    • PHP-FPM backend will attempt to use memcached and object level caching
    • If the results of a MySQL database query exist in object cache:
      • Return the cached object
    • If the results of a MySQL database query don't exist in object cache, or have expired from object cache:
      • Query MySQL databse for needed data
      • Store the results in object level cache (if instructed)
  • PHP-FPM returns the completed response to NGINX
    • NGINX (having previously checked if the request can be cached) caches the response accordingly
  • NGINX returns the completed output to client


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.

Afterburner recommendations:

  • Use NGINX FastCGI/proxy output caching
  • Construct FastCGI/proxy output cache keys to avoid collisions with other applications as well as provide support to cache data differently based on mobile user agents. The cache key is constructed using the following from the request:
    • HTTP method (GET, POST, PUT, DELETE)
    • Normalized User-Agent string (normal, mobile, mobile-maybe) - this ensures content is cached differently for mobile vs. desktop data
    • Scheme (http, https)
    • Domain name
    • URL
    • Query string arguments
  • Set rules for what requests should not use output level caching (POST requests, WordPress logged in cookies)
  • Limit connections and requests per second from a single IP address (useful to slow down brute force login attempts)
  • Control your HTTP headers by explicitly setting Cache-Control, Expires and also add headers to indicate cache status (whether a request was a cache 'HIT' or cache 'MISS' via an X-Cache-Status header)


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:

  • Ability to start/stop processes as separate user/group system users. Helpful for containing applications permissions to just a specified user and group.
  • Slow log support. PHP-FPM can log requests taking longer than a configured time limit to a log file for later inspection.
  • Smaller memory footprint. Useful for cloud providers that structure pricing based on how much RAM you need.

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).

Afterburner recommendations:

  • Use PHP-FPM sockets (unless your NGINX servers are separate from servers that use PHP-FPM).
  • Use PHP's open_basedir setting on a per PHP-FPM pool to confine applications to specific directories.
  • Use slowlog and request_slowlog_timeout to log requests that take too much PHP-FPM time.
  • Use ZendOptimizerPlus opcode caching (available on GitHub or through PECL). ZendOptimizerPlus is 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.

Afterburner recommendations:

  • Use Percona MySQL. Browse feature comparisons and explore advantages of Percona MySQL over the traditional Oracle MySQL.
  • Log and monitor slow MySQL queries using pt-query-digest.
  • Utilize tools such as the aforementioned 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 recommendations:

  • Use memcached in conjunction with the object-cache.php drop-in plugin
  • Object cache expensive MySQL database queries, or calls to remote resources (such as an API or RSS feed)


optimized and tuned setup with separate web and db servers

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.