High-performance WordPress installation using Nginx, MariaDB and HHVM from scratch in Ubuntu 13.10

Not so recently I released my studio website which uses WordPress, so I had to set up a web server from scratch, I found this a good oportunity to write something helping people easily set up a high performance WordPress using the latest mainstream technology, hopefully you find this article helpful :)

 Step 1: Setting up

So you just created a new droplet, a shining Ubuntu 13.10 installation, you ssh’d into
it and are presented with a lovely virgin terminal. First things first! Update and upgrade
all installed packages

$ apt-get update && apt-get upgrade

After a few moments the command will finish and we’re ready to install all our software.
The next thing to do is creating a user so we don’t use root as it’s a very bad practice!

$ useradd deploy

We named our new user deploy you should name it whatever you want, just remember to make
the appropiate changes. Once we have our new user let’s create a home directory for it!

$ mkdir /home/deploy
$ chown deploy:deploy /home/deploy -R

We created a new directory for our user and add it as owner so she can freely work inside
her home directory.

Finally let’s create a password for our deploy user

$ passwd deploy

Use any password you want, it will be used for sudo commands as well as logging in though
ssh (if you follow the optional step we’ll use SSH keys to log in instead of the unsafe
passwords! but that step is optional).

Let’s configure the users who can use sudo

$ visudo

Comment all existing grant lines and add the following at the end of the file

root    ALL=(ALL) ALL
deploy  ALL=(ALL) ALL

Save and exit. Now only root and deploy can use sudo. Another change we should make
now is disallow root login though ssh, for this edit the /etc/ssh/sshd_config file and
add the following at the end

PermitRootLogin no

Finally restart the ssh service so it loads the changes

$ service ssh restart

So far so good! Let’s log out root and ssh as deploy instead, from now on we’ll work
with that user.

Let’s secure our server a bit

$ sudo apt-get install fail2ban

Fail2ban is an app which bans a certain IP from logging into your server if it has failed
the password too many times, it comes with nice defaults so that will do.

Another thing we have to do is enable the firewall, this is easy

$ sudo ufw allow 22
$ sudo ufw allow 80
$ sudo ufw allow 443
$ sudo ufw enable

We only allow the ports for ssh (22) and nginx (80 and 443 if https).

 Step 1.5: Public key authentication

If you want to secure your server a bit more we can require all ssh sessions to
authenticate using a public key instead of a password, this is considerably safer.

First step is creating a ~/.ssh/authorized_keys file and pasting your public key
(most likely id_rsa.pub) there.

$ mkdir ~/.ssh
$ vim ~/.ssh/authorized_keys

Once you have saved your public key there let’s change the permissions of that file

$ chmod 400 ~/.ssh/authorized_keys

Now try ssh-ing as deploy, you should be able to log in without typing the root
password (your public key might require a password, that’s up to you).

The final step is to require ssh to authenticate only with public keys, edit /etc/ssh/sshd_config once again and append this line:

PasswordAuthentication no

Finally restart ssh

$ sudo service ssh restart

That’s it!

 Step 2: Setting up the webserver

We’ll use Nginx as our webserver, thankfully setting it up is quite easy!

$ sudo apt-get install nginx

That was fast. Let’s move onto MariaDB, because Ubuntu doesn’t include a package for MariaDB we have to manually add a package repository so we can easily install it with apt-get:

$ sudo apt-get install software-properties-common
$ sudo apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 0xcbcb082a1bb943db
$ sudo add-apt-repository 'deb http://download.nus.edu.sg/mirror/mariadb/repo/10.0/ubuntu saucy main'

Note: If you are not running Ubuntu 13.10 you can see what package repository to use in MariaDB official docs.

We can now install MariaDB as follows

$ sudo apt-get update
$ sudo apt-get install mariadb-server

It will prompt us for a password, choose a password for your MariaDB root user and
continue, the installation is pretty quick. Once it ends, let’s secure it running

$ mysql_secure_installation

It will prompt us quite a lot of questions, if you don’t want to change the root
password answer No to the first one, the rest are Yes.

Finally let’s install HHVM, Facebook’s PHP interpreter. Because HHVM uses JIT
compilation code execution is quite fast, together with Nginx we’ll make WordPress fly!

Here we have the same issue as with MariaDB, let’s add the custom package repository:

$ wget -O - http://dl.hhvm.com/conf/hhvm.gpg.key | sudo apt-key add -
$ echo deb http://dl.hhvm.com/ubuntu saucy main | sudo tee /etc/apt/sources.list.d/hhvm.list

Now install it

$ sudo apt-get update
$ sudo apt-get install hhvm

That will install HHVM in our server, there is one final step, we have to configure HHVM to work with Nginx, luckly this is super easy!

$ sudo /usr/share/hhvm/install_fastcgi.sh

Let’s try it out

$ sudo echo "<?php phpinfo(); ?>" > /usr/share/nginx/html/phpinfo.php

If you navigate to http://yourip/phpinfo.php you should see a little message saying
“HHVM”. Success!

 Step 3: Installing WordPress

Let’s install WordPress, to make it easy I’ll make a soft link to the nginx public
folder and set deploy as owner

$ sudo ln -s /usr/share/nginx/html ~/www
$ sudo chown deploy:deploy /usr/share/nginx/html -R

We can now just work inside ~/www

$ cd ~/www
$ wget http://wordpress.org/latest.tar.gz
$ tar -xvzf wordpress.3.x.x.tar.gz
$ mv wordpress/* .
$ rm -rf wordpress
$ rm wordpress.3.x.x.tar.gz

The above code will download wordpress, extract it into wordpress/ and move the
contents to that folder to our www root, finally it removes the empty wordpress folder
as well as the .tar.gz file.

If you navigate to your site index you’ll be prompted to install WordPress, let’s first
create a database

$ mysql -u root -p

MariaDB will ask for your root password, once you enter it we can create a new database

$ CREATE DATABASE wordpress;

That’s it! We have our database, I named it wordpress but you can choose any name you
want. Finally exit the SQL prompt

$ exit

Now proceed with wordpress installation, use wordpress for the database, localhost
for host, root for user and the password you chose when installing MariaDB.

Good! We now have a fully functional WordPress running on Nginx + MariaDB + HHVM!

 Step 4: Installing sendmail

To be able to send mails though our server we need to install a MTA, postfix beeing the
simplest option.

$ sudo apt-get install sendmail
$ sudo sendmailconfig

Just accept everything the configuration asks, it has pretty good defaults. To test
your sendmail installation you can run

$ echo "Hello!" | sendmail myemail@gmail.com

If it takes like a minute to send, you have to configure your IPv4 hostname

$ sudo vim /etc/hosts

And add the following line localhost.localdomain localhost HOST_NAME

HOST_NAME is the value the command hostname returns.

If you have a domain you can replace the code above with mail.mydomain.com mydomain.com HOST_NAME

Restart sendmail and try again

$ sudo service sendmail restart
$ echo "Hello!" | sendmail myemail@gmail.com

It should now work pretty quickly!

 Step 5: Optimization

Let’s start optimizing! First lets make Nginx cache our static assets as well as gzip
js and css files. To do this let’s update the global nginx configuration file

$ sudo vim /etc/nginx/nginx.conf

The gzip section must look like this

# Gzip Settings

gzip on;
gzip_disable "msie6";

# gzip_vary on;
# gzip_proxied any;
# gzip_comp_level 6;
# gzip_buffers 16 8k;
# gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;

We’ll gzip css, json, js and xml files.

Let’s now add expiration time on our assets so browsers cache them, for this we’ll
update the per-site configuration file

$ sudo vim /etc/nginx/sites-available/default

Add the following definition inside server { }

location ~* \.(js|css|png|jpg|jpeg|gif|ico|woff|ttf|svg|otf)$ {
    expires 30d;
    add_header Pragma public;
    add_header Cache-Control "public";

We now have proper headers to our static assets so browsers can cache them!

 WordPress Cache

Another thing we can do to improve performance is using W3 Total Cache. Download it, install it and enable it.

This plugin offers several options, we’ll just use Page Cache and set it to

We now have to properly update our nginx site configuration

$ vim /etc/nginx/sites-available/default

And make the following changes

server {
    # ...

    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 'null cache';
    if ($query_string != "") {
        set $cache_uri 'null 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|/feed/|index.php|wp-comments-popup.php|wp-links-opml.php|wp-locations    .php|sitemap(_index)?.xml|[a-z0-9_-]+-sitemap([0-9]+)?.xml)") {
        set $cache_uri 'null 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 'null cache';

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

    # ...

 Pretty URLs

One final thing to do is make WordPress permalinks play nice with Nginx, normally
WordPress appends /index.php/myLink for all links when using nginx pretty urls, to fix
this we can use Nginx Helper Plugin, simply download
the plugin, install it, enable it, and that’s it! We now have pretty URLs.

If you didn’t install W3 Total Cache in the previous step, add this to your
/etc/nginx/sites-available/default site configuration

# Use cached or actual file if they exists, otherwise pass request to WordPress
location / {
    try_files $uri $uri/ /index.php?$args;

Have fun with your WordPress on steroids installation!


Now read this

Using Vim for web development

With all the fuss on Atom and Sublime Text, I decided to talk a bit about my favourite editor, Vim. I’ve tried both Sublime and Atom, I’ve especially used Sublime for quite some time before comitting to Vim, but ever since I’ve... Continue →