High Performance WordPress Part 3

Hey folks I’m back with Part Three of the High Performance WordPress guide. If you’ve not done so already, be sure to check out Parts One and Part Two which will take you from a position of having no server to a fully configured VPS machine running the kickass nginx web server and mysql – or a LEMP environment as those in the know like to call it.

Today, we’ll proceed with setting up WordPress on our shiny new VPS server.

Create an nginx Virtual Host

First things first, let’s smarten up our nginx configuration to make things play nice with WordPress. I’ve learned to love nginx conf files. In the beginning it was a bit different from configuring Apache but once you get beyond this initial learning curve you realise nginx really is a very very clever little fellow that’s been really well designed.

nginx’s nerve centre can be found in /etc/nginx/

We’ll be spending most of our time in 3 place within this directory:

  • nginx.conf – the master nginx config file which contains global host configuration information
  • sites-available and sites-enabled – these 2 apache’esque directories control your virtual host website specific configuration files. Host config files are created in sites-available and symlinked back to sites-enabled – very similar to how Apache works. We’ll show you this in more detail very shortly.

Editing nginx.conf

I’m not going to go into massive detail on editing your overall nginx.conf in this tutorial. You’ll find tons of stuff in Google on this if you’re that way inclined. The default config works pretty well out of the box. The most important thing to ensure is that you have these 2 lines in your nginx.conf:

[sourcecode]

##
# Virtual Host Configs
##

include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;

[/sourcecode]

These 2 lines simply tell nginx to also load any conf files found in these 2 locations when it is being started/reloaded. These is critical for the rest of this tutorial.

Create a home directory for your websites

For simplicity, I like to store my websites in /srv/www but this is pure personal preference. You choose a different location like /var/www or /home/site/www/ if you wish. For now let’s assume you’ll follow along with me.

[sourcecode]

mkdir -p /srv/www/engin.ex/public_html

mkdir -p /srv/www/engin.ex/logs

[/sourcecode]

One directory will serve your content while the other is for your log files. Pretty simple eh?

Now let’s tell nginx about our new site location.

Creating an nginx virtual host for WordPress

Ok so let’s make an assumption that you wish to run WordPress on a domain of some sort (pretty safe assumption I would think!). Let’s go with engin.ex (see what I did there) as our dummy domain example. First we create a file called engin.ex in /etc/nginx/sites-available/

[sourcecode]

cd /etc/nginx/sites-available/

touch engin.ex

[/sourcecode]

Now you’ll probably want to crack open your favourite text editor and copy/paste the following nginx config and tweak it for your domain name.

[sourcecode]

# W3TC config rules based on http://elivz.com/blog/single/wordpress_with_w3tc_on_nginx/
server {
listen 80; ## listen for ipv4; this line is default and implied
#listen [::]:80 default ipv6only=on; ## listen for ipv6

# Tell nginx to handle requests for the www.engin.ex domain
server_name www.engin.ex engin.ex;
if ($host != ‘engin.ex’) {
rewrite ^/(.*) http://engin.ex/$1 permanent;
}

index index.php index.html index.htm;
root /srv/www/engin.ex/public_html;
access_log /srv/www/engin.ex/logs/access.log;
error_log /srv/www/engin.ex/logs/error.log;

# Use gzip compression
# gzip_static on; # Uncomment if you compiled Nginx using –with-http_gzip_static_module
gzip on;
gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 5;
gzip_buffers 16 8k;
gzip_http_version 1.0;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript image/png image/gif image/jpeg;

# Rewrite minified CSS and JS files
rewrite ^/wp-content/w3tc/min/([a-f0-9]+)\/(.+)\.(include(\-(footer|body))?(-nb)?)\.[0-9]+\.(css|js)$ /wp-content/w3tc/min/index.php?tt=$1&gg=$2&g=$3&t=$7 last;

# Set a variable to work around the lack of nested conditionals
set $cache_uri $request_uri;

# POST requests and urls with a query string should always go to PHP
if ($request_method = POST) {
set $cache_uri ‘no cache’;
}
if ($query_string != "") {
set $cache_uri ‘no cache’;
}

# Don’t cache uris containing the following segments
if ($request_uri ~* "(\/wp-admin\/|\/xmlrpc.php|\/wp-(app|cron|login|register|mail)\.php|wp-.*\.php|index\.php|wp\-comments\-popup\.php|wp\-links\-opml\.php|wp\-locations\.php)") {
set $cache_uri "no cache";
}

# Don’t use the cache for logged in users or recent commenters
if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp\-postpass|wordpress_logged_in") {
set $cache_uri ‘no cache’;
}

# similar to Apache Status – handy for quickly checking the health of nginx
location /nginx_status {
stub_status on;
access_log off;
allow all;
}

# Use cached or actual file if they exists, otherwise pass request to WordPress
location / {
try_files /wp-content/w3tc/pgcache/$cache_uri/_index.html $uri $uri/ /index.php;
}

# Cache static files for as long as possible – removed xml as an extension to avoid problems with Yoast WordPress SEO plugin which uses WP rewrite API.
location ~* \.(ogg|ogv|svg|svgz|eot|otf|woff|mp4|ttf|css|rss|atom|js|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|ppt|tar|mid|midi|wav|bmp|rtf)$ {
try_files $uri =404;
expires max;
access_log off;
}

# Deny access to hidden files
location ~* /\.ht {
deny all;
access_log off;
log_not_found off;
}

# Pass PHP scripts on to PHP-FPM
location ~* \.php$ {
try_files $uri /index.php;
fastcgi_index index.php;
fastcgi_pass 127.0.0.1:9000;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
}
}
[/sourcecode]

Whoa that’s a lot of code! We’ll go into what this does in another post some time. For now, you can trust me that these config rules work pretty darn well on some high traffic WordPress websites. For now, the final step is your symlink back to sites-enabled:

[sourcecode]
ln -s /etc/nginx/sites-available/engin.ex /etc/nginx/sites-enabled/engin.ex
service nginx restart
[/sourcecode]

In the config file above I would obviously substitute enign.ex for whatever your domain is of course! Don’t forget that step for your config. I’ve used this config example on a good few WordPress powered websites for the past year with no real issues. It’s pretty stable. Those of you paying attention will also have spotted that this config is also optimised for use with W3 Total Cache. More on that aswell a little bit later. For now, let’s get WordPress installed and up and running.

Install WordPress

Nothing majorly complicated here.

[sourcecode]
cd /srv/www/engin.ex/public_html
wget http://wordpress.org/latest.tar.gz
tar -zxvf latest.tar.gz
cp -rvf wordpress/* .
rm -R wordpress
rm latest.tar.gz
[/sourcecode]

These few commands above will get you your WordPress source ready for a clean install. Just before we do that, let’s hop back across to phpMyAdmin and create a new DB for our WordPress install. I won’t go into details about how to do that here – tons of stuff online to help you with that. Also, now would be a good time to head back over to Part 2 if you’ve skipped the phpMyAdmin install earlier.

Oh I almost forgot, one more thing you should do is set permissions on your public_html to be writable by the www-data user. There’s definitely one or two more secure ways of setting permissions on your web root but they always tend to have a trade off of making plugin and image uploads a little bit tricky. For now, we’ll set this and we’ll come back to a little bit more secure setup a bit later.

[sourcecode]
chown -R www-data:www-data /srv/www/engin.ex/public_html
[/sourcecode]

Right out of the gates this basically gives nginx permission to write files to your web root (nginx.conf sets the main system user account to use to be www-data, the same account Apache uses on Ubuntu). We’ll benefit from this straight away as the WordPress installer will be able to write your wp-config.php to the filesystem.

Whip up your browser and hit http://engin.ex/ – you should see the first screen of the WordPress install process. Complete the famous 5 minute install.

Tuning WordPress for nginx

Assuming you’ve made it this far you could stop right now and you’d have a pretty kickass nginx powered WordPress setup. But where would the fun be in that? Let’s crack on and soup up this server to be even more badass!

nginx and Permalinks

If you’re used to running WordPress on Apache, you’ll be definitely aware of what a .htaccess file is. Well, nginx doesn’t use .htaccess files. Everything is configured in your config file over in /etc/nginx/sites-available. But don’t fret – we included some nice WordPress rewrite rules earlier so go ahead and setup and switch on your favourite permalink config.

nginx Plugins for WordPress

Given nginx’s recent popularity a few plugins have started popping up in the community which help optimize WordPress for nginx environments which deserve a mention. One in particular is nginx Compatibility – This is a great little plugin and one I recommend as a must have. WordPress has some builtin functions to check for the presence of mod_rewrite which is an apache module used for url rewriting. Obviously nginx doesn’t have this module and when WordPress doesn’t detect it, there can be problems with your permalink structure. When this plugin is used it tricks WordPress into thinking mod_rewrite is present and any potential rewrite problems melt away.

Install W3 Total Cache

It would be a real shame to get this far in the guide and to not really turn your server into a traffic serving beast! W3 Total Cache (W3TC for short) will do just that. There are a handful of really really good caching plugins for WordPress. Other than W3TC, the other few I’ve used a lot are:

  • Super Cache – The grand daddy of WordPress caching plugins. It’s pretty damn good too. There’s not much to choose between Super Cache and W3 Total Cache. In fact many people prefer it to W3TC and have less trouble configuring it from scratch. It’s code is top notch which is to be expected given it’s written by top WordPress developer, Automattic employee and all round good guy Donncha O’Caoimh. It also plays nice with nginx once you’ve tweaked for your config accordingly. (Sidenote: we’re not covering an nginx Super Cache config today but if you’re interested Donncha himself provided one some time ago and a more recent one from Tim Purewhite which actually looks great – thanks for sharing Tim!.)
  • Quick Cache – This is new kid on the block and I’ve used it on a few WordPress powered websites running on Apache. I’ve not used it in anger on an nginx server yet. I do recall playing around with it and I don’t recall everything running smoothly but I didn’t delve further. But it works great on Apache and it’s gathering a lot of momentum recently.

Why I prefer W3 Total Cache

I’m not here to debate which caching plugin is better than the other. Tons of people quite frequently put together comparisons between this feature and that and spend more time than I ever could analysing the finer points of how many requests per second one plugin can serve over another. In reality there’s not much between all of the plugins mentioned here. I prefer W3TC simply because in my view pound for pound it gives provides the best all around feature set and performance of all these plugins in a VPS environment and it’s pretty simple to get working well with nginx. It also has support for Varnish cache purging and Opcode caching which we’ll be coming back to in Parts 4 and 5. That’s before we even mention the brain power behind this plugin. The frickin CTO of one of the busiest blogs on the planet Frederick Townes built W3TC from scratch so this plugin has had some serious road testing!

If you’re looking for a really detailed guide to W3 Total Cache do not look beyond Tentblogger’s incredibly detailed and totally awesome W3TC setup and configuration guide – thanks John!. You can safely ignore John’s permission and .htaccess settings as we’ve taken care of both earlier in this guide. But everything is pretty much bang on for a pretty well optimised W3TC setup. Go ahead and install W3TC and follow the Tentblogger setup guide.

If you’ve followed that tutorial carefully you shouldn’t have any issues. How can you check if it’s working? I would recommend starting with Disk enhanced caching for your page caching. In later tutorials we’ll get into Opcode caching. For now once you select enhanced Disk page caching and you browse to your website frontend (and you’re not logged into WordPress!) you should see some files getting written to /wp-content/w3tc/pgcache/ – make sure you click around a few pages and see that additional files and folders are being written to the disk. All good! Great – your initial W3TC config is complete. But what about our .htaccess rules I hear? Well the nginx config we setup earlier already has them built in! Your frontend should now be serving cached content and if you enable the W3TC page caching debug output you should see something like this in your websites source:

[sourcecode]
<!– Performance optimized by W3 Total Cache. Learn more: http://www.w3-edge.com/wordpress-plugins/

Served from: www.engin.ex @ 2011-11-02 22:53:52 –>

<!– W3 Total Cache: Page cache debug info:
Engine: disk: enhanced
Cache key: sample-page/_index.html_gzip
Caching: enabled
Status: cached
Creation Time: 0.044s
Header info:
Set-Cookie: w3tc_referrer=http%3A%2F%2Fengin.ex%2F; path=/
X-Pingback: http://engin.ex/xmlrpc.php
Content-Type: text/html; charset=UTF-8
Last-Modified: Wed, 02 Nov 2011 22:53:52 GMT
Vary: Accept-Encoding, Cookie
X-Powered-By: W3 Total Cache/0.9.2.4
Content-Encoding: gzip
–>
[/sourcecode]

I’d recommend switching off the debug once you’re happy everything is working correctly.

Take a deep breath and path yourself on the back.

That pretty much wraps up Part 3 – well done on making it this far! By now your new WordPress website is a finely tuned traffic munching animal. You’ll be ready to handle thousands more requests than a normal WordPress install running on Apache with no caching. In part 4 we’re going to venture further down the rabbit hole of High Performance WordPress tuning when we take a look at Opcode caching and how to stress test your new server. Feel free to raise any questions you have in the comments below and we’ll factor to them into future posts in the series!


Comments

32 responses to “High Performance WordPress Part 3”

  1. This is the best guide about “vps optimizations for wordpress” topic i have ever seen up today. Thank you very much. I have always heard very good comments about Linode. But i am not skilled in server management and Linux. I am a poor Windows guy =) Do you recommend me to go with Linode and start learning? I have read all the parts of your guide without being confused. All the codes you showed looked easy to me. My biggest concern about self-managing a vps is about how to securing it. What do you suggest on this topic? Also how many part will there be on this guide totaly and when will the next part (part 4) arriving? Thank you very much again.

    1. Hey Burçe,

      Thanks for the kind feedback!

      Linode is great and yes I would recommend getting yourself an account and go through each of their getting started tutorials which are great – see here – http://library.linode.com/

      There’s a few things you can do to secure your server which I’ll be covering in later parts of the guide. The config in the first 3 parts is pretty damn secure as it is but the one I’d recommend is switching on the Ubuntu Uncomplicated Firewall – UFW – https://help.ubuntu.com/community/UFW

      This will lock down all but the really necessary ports you need open to run your websites.

      Good luck with your learning!

      Ed

      1. Yes, please also mention about securing the vps. It will help a lot for newbies like me. Especially about that UFW. I am really interested in hearing about it. Also to make things clear about my security concern. I want to ask a specific question. In securing the windows at home within the years, what i and lots of people did is to configure a simple firewall and an antivirus maybe. These steps are the common steps. I am not configuring any extra settings. So i am thinking about the same steps are viable for Linux too, am i right? Or is it more complex? Thank you again Ed.

  2. Also, can you give an ETA for the next part Ed? Thanks.

    1. Hey there – sorry for the late reply – Part 4 should be out by Monday 🙂

  3. waiting eagerly for part 4 =)

    1. Hey Burçe – sorry for the delay – had a crazy few days – should get it published tomorrow evening,

      Ed

  4. I went through your amazing guide very smoothly until wordpress should have been installed. I can’t figure it out, why it’s not working. I get only a blank page @ mydomain/wp-admin/install.php

    1. Hey Marc, not sure what would be causing that – are there any php or nginx errors being logged? try restarting both to see if that makes any different

      service nginx restart
      service php5-fpm restart

      1. no logs there. restarts don’t help either. PHP is working (hello.php), but it seems to me, that in the nginx config is something wrong.

      2. it failed to create a symbolic link of the nginx conf.

        changed to this:

        # sudo ln -s /etc/nginx/sites-available/engin.ex /etc/nginx/sites-enabled/engin.ex
        # service nginx restart

        1. Glad you got it sorted in the end Marc.

          1. we’ll see. I just found that as a fault in your turorial – or is it correct?

            I still have to sort out the whole nginx webserver in a quieter time – from the beginning, starting with the official documantation. what went wrong is a mistery to me. actually your tutorial worked so smooth until the install of wordpress failed – i have no idea yet.

            1. Hey Marc,

              sorry I missed your comment back in November – you actually did identify an error in my tutorial – very well spotted! I’m running off to fix it now.

              Ed

          2. Man, and how it works! It’s a blast!

            I just left away the secure mysql and the phpmyadmin install – I like those things manual.

            Thank you vey much for this tutorials!

  5. I like the article and am looking to install nginx for a long time. Does your tutorial covers WordPress Multisite Setup?

    Thanks !

    1. Hi there VNU, no I’ve not tested this config for multisite installs – but there is an excellent thread in the WordPress forums covering this:

      http://wordpress.org/support/topic/nginx-php-fpm-php-apc-wordpress-multisite-subdirectory-wp-super-cache

      Hope this helps

  6. I’m no sure, but I think this line:
    ln -s /etc/nginx/sites-available/engin.ex /etc/nginx/sites-enabled/

    Should be:
    ln -s /etc/nginx/sites-available/engin.ex /etc/nginx/sites-enabled/engin.ex

    Thanks for your tutorial Ed. Very useful!!

    1. Hey Bruno – you’re correct – well spotted. Actually another commenter Marc spotted it a while back and I was sure I had corrected it but looks like I hadn’t!

      Sorted now! Thanks!

      Ed

  7. I noticed that W3TC (0.9.2.4) generates its own nginx.conf file. Should this be included inside the ‘master’ nginx.conf somehow? or replace it entirely?? Otherwise, I’m getting these kind of errors from the W3TC configuration screen:

    `It appears Page Cache URL rewriting is not working. If using apache, verify that the server configuration allows .htaccess or if using nginx verify all configuration files are included in the configuration.`

    1. Hi there,

      I’ve never used the .conf that comes with W3TC so it isn’t required. I did look into using it before writing this tutorial but if I recall correctly there were a lot of nested commands that seemed like overkill.

      Ed

    1. Actually in your comment both lines rendered the same but I did then spot the problem you were highlighting 🙂 Damn html encoding! Thanks for sharing Danny.

      Ed

  8. ghostcorps Avatar
    ghostcorps

    Hi 🙂

    Thanks for this, you have put alot of work into it!

    I have only gotten as far as copying in the server config and setting the server names.

    When I browse to the site I get an error stating that there is a redirect loop.

    I have cleared the cache and still get the issue.

    What do you think could be causing it?

    Thanks again
    D

    1. Hi there D, only seeing your comment now – my apologies for missing it.
      It sounds like your hostname might be incorrect – did you get sorted in the end?

      Ed

  9. When Disk:basic is used it works fine but when Disk:enhanced is enabled I get an error:

    “It appears Page Cache URL rewriting is not working. If using apache, verify that the server configuration allows .htaccess or if using nginx verify all configuration files are included in the configuration.”

    I did follow your tutorial step by step and installed a clean WordPress site, with only w3 total cache downloaded and enabled. Any idea why this might be happening?

    1. Hmmm – this is odd – I’m not seeing the same thing here – it could be a false positive. One way to check this is to enabled Disk:enhanced again and then fire up a separate browser (i.e. if you are using Chrome as your primary browser launch firefox and ensure you are not logged into your WordPress admin area). Then browse around your site and then check and see if W3TC is writing files to the following location:

      /wp-content/w3tc/pgcache

      if it is then it’s most likely actually working and the plugin is expecting an alternative nginx config.

      1. Yes the files are being created in the folder, just strange that there is that error, it’s the same thing Yoav mentioned in one of the earlier comments. I’m using Linode too and did the exact same configuration as this tutorial.

      2. Under ‘Miscellaneous’ in the W3Total Cache settings, do you specify a path to the Nginx config, and do you untick the option below it to verify rewrite tools?

  10. In your config files it states ” gzip_disable "msie6";”

    This should be better
    gzip_disable “MSIE [1-6]\.(?!.*SV1)”;
    source: http://robsanheim.com/2008/02/07/beware-the-default-nginx-config-old-ie6-hates-gzip/

    also “"” does not work in the config.

  11. Also, “&” should be “&” in the code section.

Leave a Reply

Your email address will not be published. Required fields are marked *