Matt Kirman

How to speed up WordPress with Nginx

Recently I’ve wondering whether it was worth trying to improve my WordPress-based blog performance. While it wasn’t particularly terrible, waiting over 2 seconds just to load a single page isn’t exactly stunning.

I took a typical approach and installed the WP Super Cache plugin. Page loads dropped down to ~1 second. Much better, but there must be some more performance gains to be found somewhere without modifying the WordPress source code itself.

Inspired by this post by Mark Maunder I decided to see if I could shoehorn WordPress into a similar reverse proxy/persistent process mould. So far, it works brilliantly.

With the following tweaks I managed to go from 12 requests/s to over 30 requests/s on the same hardware - a single 256MB Rackspace Cloud server - whilst reducing latency from ~2 seconds to an average of only 0.4 seconds.

Step 1. Configure Apache

You’ve really got to be running Apache with mod_php. Even if you stop reading here you’ll still find that your server performs better. In order to allow more requests to be handled by Apache, disable Keepalive connections.

You’ll find at this point that your requests will slow down a bit as clients will have to create a new connection for every request. You will, however, be able to handle many more requests as you will no longer have clients with slow connections tying up processes. We’re going to rectify this slow down in the next step.

It is also important to change the IP address that Apache listens on to 127.0.0.1 so that it can only be accessed from the local machine. Additionally, you will need to change the port from the default port 80 to, for example, port 81. This is because Nginx will need to run on port 80.

#/etc/apache2/ports.conf

# Listen 80
Listen 127.0.0.1:81

Step 2. Configure Nginx as a Reverse Proxy

Nginx is a “high performance web server and a reverse proxy server” that’s capable of handling thousands of concurrent users whilst occupying only a few megabytes of memory.

To install Nginx on Debian/Ubuntu distributions it’s as simple as:

$ sudo apt-get install nginx

Initially we’re just going to create an Nginx virtual host that acts as a reverse proxy to our persistent Apache processes. If you have never worked with Nginx before this may sound a little daunting so I’ve copied the important parts of my conf file below:

server {
  listen 80 default;
  server_name mattkirman.com; # Change this!

  add_header Cache-Control public;

  location / {
    proxy_pass http://127.0.0.1:81;
    proxy_buffering on;
    proxy_buffers 12 12k;
    proxy_redirect off;

    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_set_header Host $host;
  }
}

Because we’re running WordPress behind a proxy it’s important that we forward information such as the website host name and the client IP address. By adding proxy_set_header Host $host we can make the reverse proxy completely transparent. We’ll make use of the X-Forwarded-For header later.

Unfortunately, if you omit the proxy_set_header Host line you will no longer be able to log into your WordPress installation and any internal links may cease to work.

At this point you want to enable Keepalive requests on Nginx and crank their timeout to as high as you dare. This will offset the slow down you experienced by turning off Keepalive requests on Apache.

The beauty of this setup means that the time Apache now takes to handle a request is due solely to the amount of CPU time it takes to actually handle the request - there’s zero latency from a loopback request.

Child processes are now no longer tied up by clients with slow connections meaning that each child can process many more requests per second. It’s therefore possible to handle a huge amount of traffic without having to increase the number of processes and RAM.

Step 3. Configure WordPress

There are a few modules in WordPress (mostly based around the comment system and plugins such as Akismet) that use the client remote IP address. Unfortunately, with our current configuration $_SERVER['REMOTE_ADDR'] will always return 127.0.0.1.

In order to fix this we just need to set $_SERVER['REMOTE_ADDR'] to the value of the X-Forwarded-For header that we set in our Nginx config. You can do this by adding the following code to the top of your wp-config.php.

if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
  $ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
  $_SERVER['REMOTE_ADDR'] = $ips[0];
}

Why wp-config.php? It’s because it’s one of the only files that isn’t effected by WordPress updates. Deploy your wp-config.php to your server, sit back and enjoy your new speedy WordPress blog.

Tl;dr:

Comments