Stack Guides & Tips

Installing and configuring the fastest possible WordPress stack on Ubuntu 16.04

Published · Last updated
Historical guide – Ubuntu 16.04 is end of life

Ubuntu 16.04 LTS reached end of life back in 2021 and no longer receives security updates, and the PHP 7.0 and Percona Server 5.7 used below are long superseded. I have kept this guide intact as a reference, but please do not build a new server from it.

If you are setting up a fresh server today, follow my current guide, Installing the fastest WordPress stack with Ubuntu 18.04 and MySQL 8, which uses a newer Ubuntu LTS and MySQL 8. The steps below remain here for anyone still maintaining an older Ubuntu 16.04 box.

This article presumes you have a fresh Ubuntu 16.04 server and you can log into it. I recommend using SSH keys if you can, but that doesn’t affect this article.

Install PerconaDB

Run the following commands to install PerconaDB:

wget https://repo.percona.com/apt/percona-release_0.1-4.<span class="k">$(</span>lsb_release -sc<span class="k">)</span>_all.deb
dpkg -i percona-release_0.1-4.$(lsb_release -sc)_all.deb
apt-get update
apt-get upgrade
apt-get install percona-server-server-5.7 -q -y

Note: The Percona installation will ask you for a root password. This is the root password for your database and nothing to do with the root password for your server. If you wish, leave the password blank and it will still be secure because remote logins will be disabled.

Install Nginx and PHP 7

Run the following to install Nginx and PHP 7:

apt-get install nginx -y
apt-get install php -y
apt-get install -y tmux curl wget nginx php7.0-fpm php7.0-cli php7.0-curl php7.0-gd php7.0-intl
apt-get install -y php7.0-mysql php-memcached php7.0-mbstring php7.0-zip php7.0-xml

# now we need to temporarily set up nginx to work over HTTP so we can install WordPress and create SSL certificates
git clone https://github.com/dhilditch/wordpress-rocket-stack /root/wordpress-rocket-stack/
cp /root/wordpress-rocket-stack/etc/nginx/* -R /etc/nginx/
rm /etc/nginx/sites-enabled/default.conf
ln -s /etc/nginx/sites-available/install.wordpress.rocketstack.conf /etc/nginx/sites-enabled/

wget https://wordpress.org/latest.zip -P /var/www/
apt-get install unzip
unzip /var/www/latest.zip -d /var/www/
mv /var/www/wordpress /var/www/rocketstack
chown www-data:www-data /var/www/rocketstack -R
rm /var/www/latest.zip</pre>
<pre>#if we are on a single CPU host, run the following
mkdir /etc/systemd/system/nginx.service.d
printf "[Service]nExecStartPost=/bin/sleep 0.1n" &gt; /etc/systemd/system/nginx.service.d/override.conf
systemctl daemon-reload

service nginx restart

Ok – so now point your domain name at the IP address of your server, and when you visit you should see the WordPress installation screen.

We need a database, and we also would like SSL for *after* WordPress installation – unfortunately, installing WordPress using SSL is buggy, but it’s easy to switch AFTER installation.

Connect to mysql using:

mysql

Or if you entered a password for root, you’ll need to connect using this:

mysql -u root -P

Then run the following SQL:

create database rocketstack;
grant all privileges on rocketstack.* to rs@localhost identified by 'CHOOSEASTRONGPASSWORD';

Visit your domain URL through a browser and you’ll see the WordPress installation screen. Choose your language, then the 2nd screen will ask you for the database name, database user name, and database password – use the strong password you used in the last line.

Once you have entered those details, continue through to finish your WordPress installation. Once done, we will install SSL and the rest of the components.

Install SSL using Letsencrypt

Letsencrypt gives you free SSL certificates and I’ve simplified the scripting of this for you.

git clone https://github.com/letsencrypt/letsencrypt /opt/letsencrypt
mkdir /var/www/acme/
chown www-data:www-data /var/www/acme

Edit /etc/nginx/snippets/acme-challenge.conf and replace yourdomain.com with your actual domain – this is on one line, and that line looks like this:

 return 307 $scheme://acme.yourdomain.com$request_uri?redirect=yes;

So, if for example you have the domain www.superspeedyplugins.com, it would become:

return 307 $scheme://acme.www.superspeedyplugins.com$request_uri?redirect=yes;
Once done, restart nginx and then run letsencrypt:
service nginx restart
/opt/letsencrypt/letsencrypt-auto certonly -a webroot --webroot-path=/var/www/acme/ -d yourdomain.com -d www.yourdomain.com --agree-tos
It will ask for your email address which is useful, but below we set up auto renewal.
Edit your cron jobs with:
crontab -e
And add a line like the following, but with your domain(s) instead:
0 5 1 * * /opt/letsencrypt/letsencrypt-auto certonly -a webroot --webroot-path=/var/www/acme/ -d yourdomain.com -d www.yourdomain.com --renew-by-default

Now, we want to disable the temporary nginx conf file used and replace them with the production settings.

Edit the file /etc/nginx/sites-available/varnish.rocketstack.conf and replace the 2 SSL lines with the correct location of your newly generated SSL files – this involves simply replacing yourdomain.com on these 2 lines:

ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;

Once you have edited that file, we will remove the temporary nginx config and enable production config.

ln -s /etc/nginx/sites-available/wordpress.rocketstack.conf /etc/nginx/sites-enabled/
ln -s /etc/nginx/sites-available/varnish.rocketstack.conf /etc/nginx/sites-enabled/
rm /etc/nginx/sites-enabled/install.wordpress.rocketstack.conf
service nginx restart
If you refresh your WordPress site in your browser, it should automatically redirect to https now.
So far, we have PerconaDB, PHP 7 and Nginx configured. But Varnish and Redis are not yet installed.

Install and configure Varnish

apt-get install varnish
cp /root/wordpress-rocket-stack/etc/varnish/default.vcl /etc/varnish/default.vcl
service varnish restart
That’s that – the default.vcl is configured to work properly with WooCommerce and WordPress comments as well as other popular plugins.

Installing Redis

apt-get install redis-server
apt-get install php-redis
Edit /etc/redis/redis.conf and append the following 2 lines:
maxmemory 128mb
maxmemory-policy allkeys-lru
You can alter the maxmemory how you wish for the RAM you have available. Restart Redis to use the updated config with:
service redis-server restart
service php7.0-fpm restart

For more detail on getting the most out of Redis as an object cache, see Configuring Redis for WordPress and WooCommerce for Maximum Performance.

Optimising PerconaDB Config

Edit the following file /etc/mysql/my.cnf and replace/add the following config:
[mysqld]
key_buffer = 512M
max_allowed_packet = 512M
thread_stack = 192K
thread_cache_size = 8
query_cache_limit = 512M
query_cache_size = 4192M #(or 0 can improve performance, it depends)
innodb_buffer_pool_size = 20000M #(according to your memory - as a guide, use 60% of your memory here)
innodb_buffer_pool_instances = 16 #(according to how many cores you have)
innodb_io_capacity = 5000 #(because Digital Ocean use fast SSDs so we don't want InnoDB to be limited)
Adjust the above according to how much RAM you have. The above is based on a 30GB server, so if you have 4GB, divide all the M values by 10, e.g.
[mysqld]
key_buffer = 51M
max_allowed_packet = 51M
thread_stack = 19K
thread_cache_size = 8
query_cache_limit = 51M
query_cache_size = 419M
innodb_buffer_pool_size = 2000M
innodb_buffer_pool_instances = 16
innodb_io_capacity = 5000

Installing and configuring fail2ban

Fail2ban is a great tool for protecting your server. By banning bots through the firewall you get far faster performance than using plugins like WordFence.
sudo apt-get install fail2ban
cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

Create /etc/fail2ban/filter.d/wordpress.conf and add the following lines:

[Definition]
failregex = ^.*POST.*(wp-login.php|xmlrpc.php).* (403|499)

Then edit /etc/fail2ban/jail.conf:

Change bantime at top to 86400 (1 day instead of 10 minutes)

Find [nginx-http-auth] and add below it:

enabled=true

So, it should look something like this:

[nginx-http-auth]

enabled = true
filter = nginx-http-auth
port = http,https
logpath = /var/log/nginx/error.log

Add the following after the nginx section in the jail.conf:

[wordpress]
enabled = true
port = http,https
filter = wordpress
logpath = /var/log/nginx/*access.log
maxretry = 10

Then restart fail2ban with:

service fail2ban restart

For fail2ban to ban the right offenders behind Cloudflare, nginx needs to log the visitor’s real IP rather than Cloudflare’s. See Configuring NginX to pass through real IP Addresses from Cloudflare for the details.

Installing and configuring W3 Total Cache

I’ve tried all of the caching plugins, but the free version of W3 Total Cache is the best – just be sure to avoid using it to compress files (we already compress through Nginx) and don’t use their minification (if you want minification, use Cloudflare instead).

Here are the settings I tend to use:

Page caching: Disk Enhanced

Object caching: Redis

Browser Caching: Yes, then modify browser caching settings to enable adding the expires header and all other headers, but untick ‘compress’ files.

Reverse proxy: Tick the checkbox and enter 127.0.0.1 in the textarea so your local varnish cache can be purged.

Cloudflare Configuration

The above configuration files include configuration to pass through the correct IP addresses from your visitors, even when using Cloudflare, meaning fail2ban will work properly, but there are still some Cloudflare settings to edit:

Caching -> Browser expiration -> change to 1 year
Caching -> Caching Level -> change to No Query String
Speed -> Autominify -> tick JS and CSS
Speed -> Rocket Loader -> change to automatic
Crypto -> SSL -> Change to FULL

For a fuller walkthrough of the settings I recommend, see Configuring Cloudflare for WordPress and WooCommerce.

Results with GTMetrix

The GTMetrix score, like all the page-speed analysers (Pingdom etc) doesn’t actually take server-response-speed into account in their score. That might sound odd, and it is.

The speed that matters the MOST for your website being able to scale is the TTFB – time to first byte. It’s important that uncached this stays below 500ms. You can install the Query Monitor plugin and you’ll see the uncached TTFB along your admin bar.

Running GTMetrix over a site I installed the above stack onto, I get the following results:

Note: you have to click the ‘Waterfall’ tab to view the TTFB speed:

That’s it.

Further reading

The current stack guide on a supported Ubuntu LTS
Fine-tune and customise every part of the stack
An alternative high-performance stack build
Spin up the Droplet this stack runs on

137 comments

  1. Luigi

    Thank you very much for this guide. What i wish to see if possible is another layer of how to control and maintain this installation in order to avoid having a broken website/server.
    That would be really fabulous if you could explain.

    Thanks,
    Luigi

  2. andrew

    What is better for a large woocommerce affiliate store - MariaDB or PerconaDB?

    1. Dave Hilditch

      Both are far better than plain MySQL, but PerconaDB is better because it comes with better performance analysis tools.

  3. Danny

    Hi Dave,
    Very interesting article, but there's little info about the setup. For the average webmaster it will be hard to control and debug... and the paid services are out of reach for many. I'm curious why you use Varnish when in the serverpilot guide you use cloudflare to replace it . I'm setting up a daily deals site, and some posts need to be changed or deleted every day, so I'm curious how long pages are pages cached, or how to configure it. Or maybe I can decide not to use it and just skip the varnish setup? And what about redis? Perhaps the serverpilot setup is more suitable for those less familiar with configuring a server?

    1. Dave Hilditch

      Varnish only caches pages for a VERY short amount of time - circa 5 minutes typically. It's used to handle RUSH traffic.

      For page-caching, using something like W3 Total Cache you can control how long the page cache exists, and you can also provide an XML sitemap to prime the cache.

      For sure, the Server Pilot set up is probably simpler - especially because Varnish cache can be technical to configure if there are any plugins that need configured that aren't included in my scripts.

      Redis is awesome for object caching - even with page caching in W3 Total Cache I will typically use disk-based caching because this allows W3 Total Cache to pump these pages out without loading all of WordPress core (if you use Redis for page caching, it has to load all of WordPress core).

      1. Danny

        The drawback of serverpilot is that when you install other applications on the server then serverpilot could screw them up. So I will probably go far this rocketstack. My only concern is the caching, having read about Simple Ajax Chat in the cache busting list: what happens if a logged in admin views a front-end page. Will the admin bar at the top, any admin edit buttons on the page, etc. get cached, so the next visitor will see that, or how does that work? As that's no admin url, there's no url parameters, and it doesn't look like the extra content is added via js. Is it possible to just disable / override varnish for the time being? And how does one test plugins and themes for cache busting behavior?

        1. Dave Hilditch

          Yes - you can just skip installing varnish. Or follow the full guide and just switch varnish off. It's clever enough that if Varnish isn't working, is broken, or is off, then nginx just passes the web requests directly to PHP/WordPress.

          re: Simple Ajax Chat - I highly recommend you use a different plugin.

          re: Admin bar - these don't get cached because there are varnish rules to avoid caching for logged in users.

          re: testing for cache busting behaviour - typically most plugins don't even qualify for needing testing - i.e. most plugins apply content to your pages that applies to all users. Plugins that require testing are the ones that create different content per user - e.g. shopping baskets, user wishlists, users recently viewed products, or plugins that do messaging between users (or between users and admin). To test, view a page (logged out) with user-specific content in browser 1 (e.g. add something to your wishlist, or send a message with simple ajax chat or similar), then immediately view the same page in browser 2 (also logged out). If browser 2 sees browser 1's user-specific content then they have not implemented their code in a page-caching compatible way.

  4. Emiel

    Hi Dave,

    Thanks for this awesome post!

    I was wondering if you have any advise on a server management tool to go with this setup, like Vesta, Froxlor or ISPManager. Really curious which server management tool would you advise.

    Thanks for your reply in advance.

    1. Dave Hilditch

      I've used Ajenti in the past, but personally I prefer to modify the files directly - much more control and confidence that the server-management tool isn't ruining performance.

      Also - once you start using a server management tool, (with all the ones I've tried) it becomes impossible to alter the config files manually since it needs to have exclusive control.

      But yes - there's nothing to stop you adding the ones you listed provided you can live with the constraints above.

  5. Emiel

    Would it not be easier to install the "one-click" LEMP on 16.04 droplet image from Digital Ocean and install and configure PerconaDB, Varnish, Redis, fail2ban and Letsencrypt afterwards? Or would that result in a much different setup?
    Besides that, would it also be possible to have multiple Wordpress installs under different users on the same droplet using this server stack?
    Thanks for your reply in advance. Looking forward to it!

    1. Dave Hilditch

      Yeah sure - you can do it that way too - of course - there's many ways to skin this cat - I've shown how to do it from scratch.

      re: Multiple wordpress installs, if you're planning to have different users on the one droplet, you should probably choose a server-management tool too so that you have an interface for your users. Which one you choose will/may limit your stack behind the admin tool however.

      1. Emiel

        Thanks for your replies Dave!

        I guess it's a lot of work to manage users, user quota etc from the command line, right? We don't want to offer a server management interface to our clients, but were considering installing a server management tool to be able to manage users and their limits easily ourselves :)
        Would you (based on the possible performance loss or another reason) advise us to learn managing users and user quota from the command line or would you recommend installing a server management tool?

        Another question: Do you prefer Nginx only instead of Nginx as a reverse proxy for Apache in general?

        Thanks already for your reply, very much appreciated!

        1. Dave Hilditch

          I've seen situations where people make nginx + apache work - specifically, the only real reason to do this is if you absolutely need .htaccess rules which apache provides.

          Otherwise, apache is a memory hog and should be avoided.

          Edit: re: managing users, it depends on your server-admin skill levels and the volume of users you'll be managing. And it depends what you're aiming for with these users. You can't give servers reboot capabilities when there are multiple users on the server, amongst of whole bunch of other stuff - I'm in the practice, now that droplets are so cheap, of using 1 droplet per website, so that would be my primary recommendation, but if you're aiming to have 20 to 100 users on one droplet in order to get economies of scale, then sure it'll work better perf-wise with this stack, but still, one or more users will run some batch jobs which screw stuff up for others which is where the admin tools can be handy to make it trivial to restrict and control resource usage.

          Depends what you're doing. Personally, as a user, I'd prefer a whole droplet to myself. I can reboot it, install stuff, add whatever, reconfigure, all without affecting others. And I have confidence that my resources are my own and any perf issues are not because of contention.

▲
▼
1/1