Powerful and Cheap WordPress Blog Setup with PHP 7.0, NGNIX, Memcached and MariaDB 10.1 on DigitalOcean 512MB Droplet

I have a few wordpress blogs now, some of them had some spikes in visits, some are quite unpopular, but still I always wanted an easy and secure system to host them all without sacrificing speed and money.

After many trials and errors, I am quite happy with my actual setup which is

  • PHP7
  • Ubuntu 14.04
  • Nginx+Memcached
  • WordPress

All hosted on a 512MB droplet on DigitalOcean (ref.link) but you can easily use any VPS provider. I just like the overall DigitalOcean service and support.

Requirements

This tutorial is based on Ubuntu, if you use Centos7 you will probably have to change the installation package part, but it shouldn’t be a big deal.

  • A 512 Droplet from DigitalOcean or your preferred provider
  • Ubuntu >= 14.04

Before creating a droplet I advise you to go into your security settings digital ocean control panel and add the public ssh key of your pc/mac.

This will be useful once you will create your droplet because you can select the aforementioned ssh key and auto configure the new droplet to use it.
This way, the system won’t send you a password and the private key you own will be very secure.

digitalocean_select_keys

32bit or 64bit?

If you want to get the best usable memory, choose 32bit.
It might sounds old, but 64bit programs takes more memory, so if you plan to stay on the cheap for a lot, better go for the 32bit.

I am personally running a 64bit system and it works perfectly with 6/7 blogs right now.

Backing it up?

If these blogs are important to you, select the backup options digital ocean offers you 😉 it will make your life easier in case of a crash or problem.

Securing the system

Once you got your droplet up and running it’s good to secure it a bit.

If you didn’t take the time to use the public key authentication to access through ssh, you can do it now by following this guide (chapter 4) and come back here once you’re done.

Now that you can log on securely through public key authentication, it’s good to setup a little bit of firewall and security.

This isn’t a very advanced security practice, so feel free to add anything you think it’s worth.

Install & Configure Fail2Ban

To install just type

sudo apt-get install fail2ban

then copy the default config with

cp /etc/fail2ban/fail2ban.conf /etc/fail2ban/fail2ban.local
cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

The default config should already handle SSH banning, but if you want to change the ban time, the max retries or so on go and edit the jail.local file

sudo nano /etc/fail2ban/jail.local

Add some extra iptables config

Fail2Ban uses iptables to dynamically change the config.
If you haven’t configured it here it is a simple code to start with closing all the ports except the common ones (22 SSH, 80/443 HTTP/HTTPS).

sudo iptables -A INPUT -i lo -j ACCEPT
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT
sudo iptables -A INPUT -j DROP

Restart Fail2Ban

Now that everything is in place, you can stop and restart fail2ban.

sudo service fail2ban stop
sudo service fail2ban start

To check that fail2ban is working properly execute

sudo iptables -S

If you see something like

-P INPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
-N fail2ban-ssh
-A INPUT -p tcp -m multiport --dports 22 -j fail2ban-ssh
-A INPUT -i lo -j ACCEPT
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
-A INPUT -j DROP
-A fail2ban-ssh -j RETURN

Then you’re good to go.
Want to dig deeper? Go and read the digital ocean guide on Fail2Ban

Now let’s move on and add some swap.

Adding some Swap

I always go for the fast way to add swap.

DigitalOcean uses SSD so it’s better to use the swap as much as we can since we are low on memory.

sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile

These commands will create a 2GB swap file and activated, but it won’t still be permanent.
To do that, edit the fstab with

sudo nano /etc/fstab

and add a new line with this

/swapfile   none    swap    sw    0   0

This should be enough for us.
To deep tune the swap, check out the guide on DigitalOcean

Now that our droplet is ready, we can start installing php and nginx.

Installing PHP7 & Nginx & MariaDB 10.1 & Memcached

To install PHP7 & Nginx & MariaDB in the latest version we’ll need to setup our ubuntu with a few new repos.

Here it is the short configif for ubuntu 14.04 (trusty).
In case you’re targeting a different ubuntu distro, replace trusty  with the codename of your distro.

In case you aren’t sure or the repos are outdated, checkout the Nginx Download page and the MariaDB download page.

This setup will also add a custom repo for php7 made by Ondřej Surý who maintains the php packages in Debian and offers to anyone a PPA for PHP 7.0 on Ubuntu that we’ll be using.

To setup the repos use

sudo add-apt-repository ppa:ondrej/php
sudo add-apt-repository 'deb http://nginx.org/packages/ubuntu/ trusty nginx'
sudo wget http://nginx.org/keys/nginx_signing.key
sudo apt-key add nginx_signing.key
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 [arch=amd64,i386] http://mariadb.cu.be/repo/10.1/ubuntu trusty main'
sudo apt-get update

Once you’re ready install it all with

sudo apt-get install php-common php-memcached php7.0 php7.0-cli php7.0-fpm php7.0-gd php7.0-json php7.0-mysql php7.0-xml php7.0-opcache memcached mariadb-server-10.1 nginx

MariaDB will ask for a root password, choose a strong one.

Once done, secure the MariaDB Mysql installation with

sudo mysql_secure_installation

There shouldn’t need much more to do (you have already a root password, and MariaDB hasn’t added test databases).

You can now move on to configuring NGINX.

Upgrading from php 5.6?

In case you’re using this guide to upgrade to PHP7 and you already used one PPA by Ondřej Surý you might want to try this in case upgrading doesn’t work

sudo apt-get -y purge php5-fpm php5-common php5-cli php5-json

Upgrading from php 7.0?

If you used some previous guide while using the old repo ppa:ondrej/php-7.0 you might incur into this error

W: Failed to fetch http://ppa.launchpad.net/ondrej/php-7.0/ubuntu/dists/trusty/main/binary-amd64/Packages: 404 Not Found
W: Failed to fetch http://ppa.launchpad.net/ondrej/php-7.0/ubuntu/dists/trusty/main/binary-i386/Packages: 404 Not Found
E: Some index files failed to download. They have been ignored, or old ones used instead.
E: Couldn't rebuild package cache

This is because the repository is now merged into ppa:ondrej/php .
To fix it execute

sudo apt-get install python-software-properties
sudo ppa-purge ppa:ondrej/php-7.0

to remove the old repository and

sudo LC_ALL=en_US.UTF-8 add-apt-repository ppa:ondrej/php

to add the new repository.
Then you can safely update & upgrade your packages.

Prepare NGINX

Since PHP7.0 FPM comes already configured nicely with socks (it listen on /run/php/php7.0-fpm.sock ) we can directly configure NGINX to use it.

To do that, you can copy the content of this gist into /etc/nginx/ffpc.conf

https://gist.github.com/andrea-sdl/338ad1cde3841ffd7ae6.js

or just execute

wget -P /etc/nginx/ https://gist.githubusercontent.com/andrea-sdl/338ad1cde3841ffd7ae6/raw/00e24040d2e89b37269a9978540688f89b83251a/ffpc.conf

Since the file is not into the nginx/conf.d directory it won’t be automatically loaded. When we need it we will include it into the configuration.

Editing the nginx.conf

Php7.0 by default uses the user www-data, while nginx will  use the user (guess what?) nginx.

This causes a bit of trouble when the nginx process tries to read/write to php fpm.
To fix that edit the nginx.conf file

nano /etc/nginx/nginx.conf

and change

user nginx;

with

user www-data;

Configuring the first WordPress on NGINX

The ffpc.conf file above will serve us in the first config of wordpress.

Let’s suppose your domain is “yourdomain.com”, go and edit the file

sudo nano /etc/nginx/conf.d/yourcustomdomain.conf

And paste there these lines

server {
  listen 80;
  server_name YOURCUSTOMDOMAIN.COM;
  root /var/www/YOURCUSTOMDOMAIN.COM/;
  index index.php;
  access_log /var/log/nginx/YOURCUSTOMDOMAIN.COM.access.log;
  error_log /var/log/nginx/YOURCUSTOMDOMAIN.COM.error.log;

  include ffpc.conf;
}

after this, create the directory

mkdir -p /var/www/YOURCUSTOMDOMAIN.COM/

and reload nginx with

service nginx reload

Be sure to replace any reference with your real domain name 🙂

Adding a new blog

To get the latest version of wordpress installed into your new blog, just do

cd /var/www/YOURCUSTOMDOMAIN.COM/
wget https://wordpress.org/latest.tar.gz
tar --strip-components=1 -zxvf latest.tar.gz

Once the php files are done, you can access the domain and configure your wordpress blog.

Security tip: don’t use the same MySQL/MariaDB user for every blog. Configure a different  user for each blog. (wondering how? Check the DO guide here)

Caching with Memcached and Autoptimize

Once you’re up and running you need two plugins to cache your wordpress pages

Autoptimize

Autoptimize will compress the css/html of your page.
These settings usually work out pretty well for any generic website.

Autoptimize Options

WP-FFPC

WP-FFPC is our true secret.
It will cache the pages into the memcached cache, making them available to the end user with no computation at all.

The user will receive the HTML and php won’t even bother working because it will be the memcached cache that’s returning the result, making it blazing fast.

Once installed you have to add the WP_CACHE config into the wp-config.php file so edit with
nano /var/www/YOURCUSTOMDOMAIN.COM/wp-config.php

And add to the top (but within the php tags)

define ( 'WP_CACHE', true );

Once ready, configure WP-FFPC by putting the memcached default address 127.0.0.1:11211

WP-FFPC options memcached

Then I also advice to extend the cache time to an entire day (86400 seconds) and even more for the taxonomy

WP-FFPC options cache

That’s it! Now your blog should be ready and blazing fast.

Backing it up, part 2

I won’t dig into the details of backing it up, but there are some tools I advice and love

  • AutoMySQLBackup to backup the databases (you will probably need a little tuning for MariaDB)
  • Tarsnap if you’re a bit on the nerdy side and want to be super-sure that everything is backed up

 

31 responses to “Powerful and Cheap WordPress Blog Setup with PHP 7.0, NGNIX, Memcached and MariaDB 10.1 on DigitalOcean 512MB Droplet”

  1. Hello,

    Thanks for the guide, will try it out today, but i want to ask few questions.

    1. Can i install nginx and Maria db separately?

    2. How can i use CDN? because the memcached plugin doesn’t have this feature.

    Like

    1. Also you still have HHVM conf on ffpc.conf

      Like

      1. Yup, you’re right, I fixed it 😉 they were leftover of my hhvm test.
        thank for pointing that out.

        Like

    2. Hi Kingsley 😉

      1. Yes, you can install separately, no big deal. I use a single command because it’s easier when you setup a new server.
      Also, if you already have mysql, you can also skip this test and move on. It might be slower, but the big step in performance is done mostly by memcached.

      2. I personally don’t use the CDN, but there’s one plugin I strongly recommend that “partly” does this for images.
      The plugin is the official jetpack plugin for worpdress. To get a speed bump use the “Photon” feature which will cache the images on their server, reducing the load

      BTW: why am I not using a CDN?
      Nginx is really performant when delivering static assets, so it’s ok to not use a CDN.

      The CDN in autoptimize is useful only if you have referenced the remote images from a CDN. Otherwise it won’t be that much o a CDN to you.

      If you want a more integrated CDN you should consider moving to the plugin W3 Total Cache. I used them in the past and makes using a CDN (like cloud front backed by amazon s3) much much easier.

      Like

      1. Hello;

        Seem PHPMYADMIN is not working on PHP7, so i will try this with PHP5.5 or PHP5.6 which do you recommend?

        Like

      2. Php5.6 should give you some boost in speed and it’s not that problematic like pho7. Go for it 😉

        Btw: be sure to secure phpmyadmin 😉

        Like

  2. Looking forward to find the time to try this.

    I’m using Linode, and I think it won’t make much difference, exceot maybe for the ssh key part, which however can be easily sorted out.

    Also, I’ve been thinking for long time to give DO a try anyway.

    Thanks for this post!

    Like

    1. Hi!

      yes, it shouldn’t be that much difficult to adapt it to Linode, write me if you have any issue, but both providers are equally good. 😉

      Like

  3. Very cool article Andrea. Sounds like a really nice stack. I was a bit surprised by the x32 vs x64 memory usage though.

    Did you do any performance tests on this stack? How does it compare to others?

    Like

    1. Hi Julien,

      I just made a test with blitz.io, consider the site is also backed by cloudflare, which also improves the caching.
      With cloudflare enabled, on a cached page, I got 12,720 successful hits in 60 seconds, which counts up as 2.747.520.000 daily visitor support 😀
      Load average was about 0.03

      When I disabled cloudflare I got a load average of 0.25 and 12,204 hits in 60 seconds, which arounds still about 2 million daily visitor support.

      It’s a total different topic if we talk about non-cached pages. The whole caching system is in place _because_ we want to allow spikes of visitors.
      Extremely high spikes of commenting are unusual, but if they could take place they would obviously take down a server with this kind of setup.

      There’s still a way to limit this and that is to change the config in the php-fpm pool and managing the options of
      pm.max_children should allow you to say how much php is concurrently running.

      I keep it to 10. Meaning that if I’ve got more than 10 concurrent operation on php, one will wait. Unless you have a super-high commenting rate, you shouldn’t notice hiccups.

      Like

  4. Why not just use something like easyengine?

    Like

    1. It’s a nice product! Never used it, but I see how it helps.
      Anyway, at the time of writing ee didn’t have php7.0 support.

      Also it’s not clear from their docs how to “import” the data from old website.

      But aside from them, if you like easyengine, try it 😉 I personally like to test things out.
      In the future I’m more oriented to have an ansible script that’ll automate it all 🙂
      this way I got control of what’s happening _and_ I can automate it.

      Like

  5. FYI, your second WP-FFPC link goes to your /wp-login.php URL. Further, great article!

    Like

    1. Link fixed! Thanks for noticing 🙂

      Like

  6. Thanks Andrea for the great post. I did 3 different setups for my wordpress site:
    1. docker-compose to spin up wordpress+mariaDB. The result is the site is very slow for many users..
    2. compile everything from source code, it turns out to be too challenging to change configurations since I am not expert of these.
    3. your guidance, it worked very well so far!

    My question: have you considered to compile php7, mariaDB, nginx from source code?

    Like

    1. Hi Xi Xiao,
      Good that it’s working for you 😉

      I am not a big fan of compiling things from zero, mostly because it makes maintaining a server too much of an hassle.
      Consider that if you compile everything you’re the one in charge to be sure that any vulnerability gets fixed asap, while with compiled software you can leave it to others and even automate the process of installing updated software to fix secure vulnerabilities.

      I was once a gentooist, and loved the distro, but it requires too much time and it’s not for me anymore

      Like

  7. btw, what is the wordpress plugin for the “post comment” zone that allows users to log in with other social sites ID?

    Like

    1. If you refer to the one used in this blog I’m only using the Jetpack comments plugin, so I guess it’s it.
      BTW I highly advise you to install it, even if only for the photon feature (a CDN for your images, right off the bat, free)

      Like

  8. hi, thanks for the guide, will try it soon.

    tell me what are you referencing exactly by

    error_page 502 = @fpm;

    thanks

    Like

    1. Hi gingibash!

      that is an old code I forgot behind. It was useful when you used primarly HHVM and wanted to fallback to PHP-FPM in case the HHVM service became unavailable (error 502). You can safely remove it, I’ve also updated the gist.
      Thanks for noticing 😉

      Like

  9. Walter Schmenge Avatar
    Walter Schmenge

    good article. in my experience for busier sites i needed 1Gb ram or else mariadb would run out of memory and die.

    Like

    1. Hi Walter, and sorry for the late reply:

      I never had issues with this, as long as you have good swap file, the caching system won’t even allow WP to get to mariadb, and since we’re hosting on SSD, even using the SWAP is quite performant. Obviously if you can have more ram, then all is even better, but I don’t think it’s so required unless you have a very active community in your blogs (commenting and so on)

      Like

  10. Have you tried Memcached is your friend plugin? It’s similar to WP-FFPC. I’m wondering which performs best 🙂

    Like

    1. never tried it, but if it works the way ffpc does it shouldn’t be that much difference. The key is to let nginx handle the memcache data retrieve, so as far as I know it’s not so important what is the plugin that’s putting it there.

      Obviously if the plugin is really non-performant then it might be a problem, but in this case I wouldn’t be worried about that.

      That said, WP FFPC is more frequently updated and has a larger userbase (at least from wordpress.org) and therefore I would prefer it over the other for this reason.

      Like

  11. […] Powerful and Cheap WordPress Blog Setup with PHP 7.0, NGNIX, Memcached and MariaDB 10.1 on DigitalOc… […]

    Like

  12. Thanks for your article… is really usefull !

    Could you help me with this error: WARNING: you’ve entered username and/or password for memcached authentication ( or your browser’s autocomplete did ) which will not work unless you enable memcached sasl in the PHP settings: add `memcached.use_sasl=1` to php.ini

    Do you know how can i know the username and password of memcache ?

    thanks

    Like

    1. As far as I know the default install shouldn’t have any user/password required. If you have this error in the wordpress admin I’d check WP-FFPC settings because the browser (chrome) autocomplete might have incorrectly added the username/pass in the “Backend Settings” tab. check it out and let me know

      Like

  13. You are correct. Thank you very much.
    Taking advantage of your knowledge, I’d like to learn new ways to improve this setup. Checking my site on sites like Google Speedtest, i saw that we need to enable gzip compression, config browser cache etc.

    Could you do a tutorial, where we can use this module in your setup:

    https://developers.google.com/speed/pagespeed/module/build_ngx_pagespeed_from_source

    Following the evaluation of my site: https://www.webpagetest.org/result/160824_HB_3HMT/

    thanks

    Like

    1. Hi Diego,

      I know that module and used in the past, but personally I wouldn’t use it in this case, we already have a quite performing solution. Yes, we can do more, but the actual result we have now is really good.

      I checked your website on https://developers.google.com/speed/pagespeed/insights/?url=http%3A%2F%2Fwww.pequetitos.com&tab=desktop and the result is good. Yes, we have a low index on mobile, but that depends on the design, not the performance, and you have small performance issues caused by theme images or so on.

      in the end, still a good result.
      Also, I’m not a big fan of compiling modules (although my first linux distro was a Gentoo 😀 ), and the reason for that is because it’s hard to maintain and update the system, packages to me are more convenient in this regards.

      Like

  14. Hi,

    Should I get memcache or memcached ?

    I am using NGINX + PHP 7 + W3 Total Cache

    I think there are some compatibility issues…

    Like

    1. I use memcached. Obviously if you use W3 Total Cache this guide might not apply as it’s another layer of caching.

      Like

Leave a comment