LowEndBox - Cheap VPS, Hosting and Dedicated Server Deals

Tutorial - The LowEndCluster - Part 2


Two weeks have passed and it’s time for the second part of the LowEndCluster tutorial series! This time around I’m going to focus on setting up the web nodes, which we will later tie together with a shared filesystem in order to make them really redundant.

Last time I mentioned I’d be using Apache for the web nodes. Even though I am a big Apache fanboy and I honestly believe there is a lot of use for it (plus I like the configuration better), for this tutorial I’m swapping it for NGINX. NGINX is a little easier on memory, handles quite some more connections out of the box, and is more than enough for a cluster running WordPress.

So, the idea is to have these web nodes running NGINX as a front-end for PHP-FPM. These NGINX instances will eventually only be accepting connections from the load balancers, but right now we’re going to keep them open to the world.

I’m deliberately not exposing PHP-FPM to the load balancers as it may be a security risk. I’m not going to go into those details right now, but you can read more about it here. Bottom line is, we don’t want people uploading files secretly containing PHP code. So, without investigation this any further, I’m going to be safe rather than sorry.

Anyway, enough talking. On to the real work!

For clarity’s sake: these steps should all be run on both web nodes!

So, let’s go ahead and log on to the two servers you have reserved for web nodes. We’re going to install a number of applications on them:

  • PHP5
  • PHP5 MySQL (works with MariaDB as well)
  • PHP5 FPM
  • PHP5 GD
  • And some other packages that come with it

I’m going to assume a recent Ubuntu or Debian-based system here. This tutorial may very well work for RHEL-based distributions as well, though the installation commands are different.

Let’s install the packages by running this command:

sudo apt-get install nginx php5 php5-fpm php5-gd php5-mysql

This was the easy part. After having done this, you should be able to go to the IPv6 of your server and see a default NGINX page. If you don’t, or if you had Apache still installed, there’s some detective work up ahead, though I won’t go into the details on that.


Now, let’s configure NGINX for your website. The configuration needs to be identical on both the web nodes, so it’s advisable to try it on one server and then copy it to the other when it works.

Go to /etc/nginx/sites-available and create a file named YOURHOSTNAME.conf with the following contents:

# Upstream to abstract backend connection(s) for php
upstream php {
server unix:/var/run/php5-fpm.sock;

server {
listen [::]:80;

## Your website name goes here.
server_name server.example.com;
## Your only path reference.
root /home/username/public_html;
## This should be in your http block and if it is, it’s not needed here.
index index.php;

location = /favicon.ico {
log_not_found off;
access_log off;

location = /robots.txt {
allow all;
log_not_found off;
access_log off;

# Deny all attempts to access hidden files such as .htaccess, .htpasswd, .DS_Store (Mac).
# Keep logging the requests to parse later (or to pass to firewall utilities such as fail2ban)
location ~ /\. {
deny all;

# Deny access to any files with a .php extension in the uploads directory
# Works in sub-directory installs and also in multisite network
# Keep logging the requests to parse later (or to pass to firewall utilities such as fail2ban)
location ~* /(?:uploads|files)/.*\.php$ {
deny all;

location / {
# This is cool because no php is touched for static content.
# include the “?$args” part so non-default permalinks doesn’t break when using query string
try_files $uri $uri/ /index.php?$args;

# Directives to send expires headers and turn off 404 error logging.
location ~* ^.+\.(ogg|ogv|svg|svgz|eot|otf|woff|mp4|ttf|rss|atom|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|ppt|tar|mid|midi|wav|bmp|rtf)$ {
access_log off; log_not_found off; expires max;

# Pass all .php files onto a php-fpm/php-fcgi server.
location ~ [^/]\.php(/|$) {
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
if (!-f $document_root$fastcgi_script_name) {
return 404;
# This is a robust solution for path info security issue and works with “cgi.fix_pathinfo = 1” in /etc/php.ini (default)

include fastcgi_params;
fastcgi_index index.php;
# fastcgi_intercept_errors on;
fastcgi_pass php;

That’s a long file and I’m not going to go into every single line. There are some that I’m going to discuss, though.

listen [::]:80;

This lines tells NGINX to listen to connection on port 80 on IPv6. The line for IPv4 is similar, just without the ‘[::]:’. I’ve let that line out as we’re not going to listen for IPv4 connections. You could, of course, do so on one of your NAT ports. That’s outside the scope of this tutorial, though.

server_name server.example.com;

Please replace the server name with your actual server name, or the domain name that is linked to your server’s IP address. This will ensure NGINX will use the right config file when trying to access your site. I may move away from this later on, but right now let’s stick to this for easiness sake.

root /home/username/public_html;

You may change this to a location that’s suitable to you. Eventually, we will be changing this to the path of our cluster filesystem. Make sure this directory exists, for now, and is owned by ‘www-data’. (You can change the owned by running ‘chmod -R www-data. /home/username/public_html’.)

That’s all I wanted to discuss for this config file. Other parts are documented, so feel free to read up on that to get some more information on those specific parts.

Let’s now enable this site:

ln -s /etc/nginx/sites-available/hostname.conf /etc/nginx/sites-enabled/hostname.conf

We’ve just added a symlink to the sites-enabled directory, so NGINX will pick up on that after restarting it and start serving the site:

sudo service nginx restart

And NGINX should be all fine and happy.

Installing WordPress

This step is currently optional, but does get you closer to a working product, and it may save you time later on (I will refer back here later anyway). You can install WordPress on one node, and then copy it to the other node. WordPress handles domain names rather stupidly. Long story short: while installing WordPress you select a domain name for that installation and that’s what WordPress will stick to (and redirect to) unless told otherwise. This means that if you install WordPress on web node A and use that node’s hostname as a domain name, it will redirect back to that domain name when you access it from web node B. In the end, this will not be a problem, as WordPress will use the domain name we will point to the load balancer.

My advice would be this: temporarily map your desired domain name to one of your web nodes, install WordPress on that one, and then copy the installation to the other node. This will give you a working WordPress installation and an easy starting point for the other steps.

So, installing WordPress. It’s actually dead simple. From the server’s root directory I’ve mentioned before, run the following command:

 wget https://wordpress.org/latest.tar.gz

Now, extract WordPress:

tar xvzf latest.tar.gz –strip-components=1

I’ve used ‘–strip-components=1’ to prevent tar from putting it all inside the ‘wordpress’ directory rather than in your document root. Now, fix the ownership of the files:

chown -R www-data. *

And open up your browser to start the WordPress installation!

The first step of the installation will be just fine, until you need a database username and password. Head up to your DB master server, log in as root (‘mysql -u root -p’) and run the following commands:

GRANT ALL PRIVILEGES ON wordpress.* TO “wordpress”@”%” IDENTIFIED BY “password”;

This creates a database named ‘wordpress’ and a user named ‘wordpress’. The user may log in from any location. You could limit that by the IP addresses of your web nodes, of course. A better approach would be to do that with iptables rather than MySQL’s system.

With these details, head back to the WordPress installation.

Now, here’s the thing… WordPress is not only a bit of a pain with domain names, it also doesn’t seem to handle IPv6 database addresses very nicely. So, when giving the WordPress installer the address of your database master, be sure to use the hostname as that does work.

Now, if all went well, you should have a fully working copy of WordPress backed up by two database servers. Even though there is no automatic failover yet, it does give you a proper backup and an easy switch in case of something going wrong.

That’s it for this time! I’ve yet to see what we’re going to do next time: the load balancers or the file system. This depends on how well my filesystem research goes.

I hope you’ve enjoyed this part and I look forward to getting your feedback!



  1. Sled dog:

    If your php is running as www-data, and you chown -R www-data your wordpress directory, then all WordPress files and directories are writable by php. This is a huge security hole. A theme or a plugin with an exploit can wreak havoc.

    May 3, 2015 @ 6:06 pm | Reply
    • Maarten Kossen:

      Yes, but it’s also necessary in order to have WordPress update itself and the installed plugins/themes.

      The actual security hole (if it is one) is a user installing a malicious plugin.

      May 9, 2015 @ 12:57 pm | Reply
  2. I like this series so far. Finally something to use all the useless servers you got which would be just idling otherwise :) will test the guides when all parts are written and published!

    May 3, 2015 @ 8:45 pm | Reply
  3. Very nice! I’m waiting to review all of the article parts to see if I could implement something like this with the 5-location VPS package I got from MegaVZ. The IPv4 IPs are NAT, but they also come with IPv6.

    May 4, 2015 @ 1:06 pm | Reply
  4. raz:

    more windows VPS

    May 5, 2015 @ 3:40 pm | Reply

Leave a Reply

Some notes on commenting on LowEndBox:

  • Do not use LowEndBox for support issues. Go to your hosting provider and issue a ticket there. Coming here saying "my VPS is down, what do I do?!" will only have your comments removed.
  • Akismet is used for spam detection. Some comments may be held temporarily for manual approval.
  • Use <pre>...</pre> to quote the output from your terminal/console, or consider using a pastebin service.

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