It started off thus:
The theme developer pushed out point update that came with no info on what all files have changed so that I could have done a quick drag and drop file replacement for patch-up work.
However, what looked like a very boring workload to proceed with, turned out to be quite interesting. Well, updating the theme, and then including my customisations wasn’t at all interesting… what was instead, was changing the architecture of the website — from LAMP to LEMP.
Since November, opensourceforu.com was hosted on the grid hosting facility of MediaTemple (gs), with a dedicated MySQL container to give it some much-needed “dedicated” boost. Caching was the responsibility of W3 Total Cache — and thus serving in excess of 100,000 pages a month since then has had become a child’s play. (The elders might remember the performance issues with the website before December 2011!) The reason for going with MediaTemple’s gs hosting was what comes as a given with shared-server type of hosting environments — security is the headache of the hosting company.
However, once you move to a VPS/Cloud environment, OS hardening all of a sudden becomes the webmaster’s responsibility — stinks for me because I wear that cap since November last year.
Well, anyhow — a couple of weeks back I did switch this website to a 512MB Rackspace Cloud Server for a little more than 24 hours for stress testing, and actually saw a performance boost (specifically the backend — which I need to use most of the time unlike our visitors). That was of course a LEMP setup (notice the E?).
Why LEMP? Well, it’s something new for me dabble with… Besides WordPress.com runs on it. And Boudhayan, long-time LFY author and my good friend, wrote a quick tutorial on switching from LAMP to LEMP in December, which sort of gave me the push to check it out for myself.
So, my recipe for those 24 hours was this:
- Debian 6.0 64-bit OS on a 512MB Rackspace Cloud Server
- Nginx 1.0.x (from the dotdeb repo) for Web Server
- MariaDB 5.3 (from official mariadb channel) for WP’s DB needs
- PHP-FPM (from the dotdeb repo) for Nginx’s PHP-FastCGI needs
- W3 Total Cache with APC taking care of object cache — for this website’s caching needs
And, fortunately, it ran smoothly enough before I made the switch back to MediaTemple’s Apache. Apart from Nginx, where I actually had to set up the virtual hosts… the rest of the pieces — MariaDB, APC, PHP-FPM — were really vanilla configs with no config changes atop what came out of the box.
Thus, the idea was planted in my (bone)head. Taking a cue from the Rackspace experience my plan was to setup a DR (disaster recovery) image ready on Rack and dump it on its CloudFiles storage.
But I had to run the website for a month to figure out any issues if we were to proceed with a LEMP stack for our future deployments. (Well, I didn’t say we’re not switching back to MT, did I? The files are lying there. I just need to import the MySQL dump, run
rsync to sync
wp-content/uploads/ folder between the two servers, and change the DNS — all this hardly takes an hour.)
It started off with GoDaddy offering 98% discount on their 1GB cloud offerings for the first month. Cool. Rs 54 for a month is literally free of cost. Time to setup.
What? No Debian.
Fine, let’s go with Ubuntu 10.04 LTS.
Too many repos to setup just to fetch the latest versions of Nginx, PHP-FPM and APC. All done, but too much effort. Delete. Fedora and CentOS as servers were out of question — not because they are bad, but due to being less familiar with those environments.
Myself being a cheapstear — I mean, who wants to spend a bomb just for PoCs? ;-) — I headed over to Hetzner.de to acquire a 2GB VPS (yeah, not cloud, but what the heck!).
So, what’s my new recipe this time?
- Debian 6.0 32-bit on a Hetzner 2GB VPS (only downside is this one offers a single core compared to Rackspace’s 4 virtual core offerings)
- Nginx 1.0.x (from the dotdeb repo) for Web Server
- MySQL 5.5 (from dotdeb repo) for WP’s DB needs
- PHP-FPM (from the dotdeb repo) for Nginx’s PHP-FastCGI needs
- W3 Total Cache with APC taking care of object and database cache — for this website’s caching needs
- Varnish 3.0 (from official Varnish repo) as the HTTP accelerator. (It’s disabled for now, because I was unable to provide a streamlined and separate mobile theme experience — the home page was desktop, but the articles were WPTouch — weird, not an expert, no clue, too lazy to carry it forward. Dumped.)
Just FYI: Our DNS layer is CloudFlare and W3 Total Cache with page and minify on disk (DB and object cache disabled), and a CDN to deliver static content (initially from Amazon CloudFront, and now from MaxCDN) were already being used with the MediaTemple setup too. Besides, the list of WP plugins also hasn’t changed.
The reason for dumping CloudFront by end of December was simple — the CSS and the JS files that were delivered by the CDN were not gzipped. I gathered text file gzipping is not offered by Amazon CloudFront — at least my pea-brain couldn’t identify any such setting on their dashboard. Being a sucker for Page Speed scores (our current score is 94/100), I made the switch to MaxCDN since enabling text file gzipping is as simple as checking a checkbox. (Note that MaxCDN has lesser Edge servers compared to CloudFlare, but they are literally half the price of CloudFront.)
Well, with that introduction over. Let’s get down to the actual configurations — based on tutorials from all over the Web.
Debian on the VPS — hardening it a bit
I chose a Debian 32-bit minimal. Hetzner took more than 12 hours to provision the VPS. Since by this time I had already given up on GoDaddy’s Ubuntu thanks to the headaches of setting up a LEMP stack on 10.04 LTS, I thought of setting up the LEMP templates on Rackspace Cloud Server — they provision the server, because of the advantages that come with Cloud offerings, in less than 5 minutes.
The idea was to get up the LEMP stack with WP support, and
rsync the configurations over to the Hetzner VPS once I get access. Rackspace bills only for the hours I use their infra, so I keep it till I get the other VPS, and then dump the backup onto their CloudFiles storage, and delete the server instance. This exercise actually gave me the idea of a DR entity that I could manage if the LFY management decides to go with Rackspace and WP atop LEMP for the long haul.
The first thing was to create a new user account:
# useradd -m <user-name> # passwd <user-name>
Now, open the
/etc/ssh/sshd_conf file, find the following directive:
…and change this to read:
This means, SSH root logins are now disabled. So, from now on, we login as a regular user and
su - to do admin stuff.
Before we setup a firewall, login over SSH as the <user-name> we created earlier to test everything is working. If successful, reload the changed SSH config into memory so that root logins are rejected by the SSH server henceforth:
Next up, the firewall:
# apt-get update # apt-get dist-upgrade # apt-get install ufw
UFW (Uncomplicated Firewall) is an amazingly easy-to-setup firewall software that I had never heard of before, but liked the fact that by using it I don’t need to dabble into the world of IPTABLES commands anymore.
The only ports that should be a available over the www is 22 for SSH, and 80 for accessing web server. So, run:
# ufw allow www # ufw allow ssh # ufw default deny # ufw enable
Running the last command will warn you that your SSH session could be disrupted — since we’ve already successfully tested SSH connections for <user-name> after disabling root logins, it’s safe to press
y to confirm. It, however, didn’t interrupt my SSH session — so much for panic-driven false warnings ;-)
Run the following command to check the firewall status:
# ufw status Status: active To Action From -- ------ ---- 22 ALLOW Anywhere 80 ALLOW Anywhere
Good enough. You might wanna read this excellent UFW guide to customise the firewall settings further to your liking.
Time to harden the OS a bit more against some of the wild attacks that the server might be subjected to.
Taking a cue from this excellent nixCraft article, open your
/etc/sysctl.conf, check what all things already exist there and then enter the rest of the following directives in that file:
# Avoid a smurf attack net.ipv4.icmp_echo_ignore_broadcasts = 1 # Turn on protection for bad icmp error messages net.ipv4.icmp_ignore_bogus_error_responses = 1 # Turn on syncookies for SYN flood attack protection net.ipv4.tcp_syncookies = 1 # Turn on and log spoofed, source routed, and redirect packets net.ipv4.conf.all.log_martians = 1 net.ipv4.conf.default.log_martians = 1 # No source routed packets here net.ipv4.conf.all.accept_source_route = 0 net.ipv4.conf.default.accept_source_route = 0 # Turn on reverse path filtering net.ipv4.conf.all.rp_filter = 1 net.ipv4.conf.default.rp_filter = 1 # Make sure no one can alter the routing tables net.ipv4.conf.all.accept_redirects = 0 net.ipv4.conf.default.accept_redirects = 0 net.ipv4.conf.all.secure_redirects = 0 net.ipv4.conf.default.secure_redirects = 0 # Don't act as a router net.ipv4.ip_forward = 0 net.ipv4.conf.all.send_redirects = 0 net.ipv4.conf.default.send_redirects = 0 # Turn on execshild kernel.exec-shield = 1 kernel.randomize_va_space = 1 # Tuen IPv6 net.ipv6.conf.default.router_solicitations = 0 net.ipv6.conf.default.accept_ra_rtr_pref = 0 net.ipv6.conf.default.accept_ra_pinfo = 0 net.ipv6.conf.default.accept_ra_defrtr = 0 net.ipv6.conf.default.autoconf = 0 net.ipv6.conf.default.dad_transmits = 0 net.ipv6.conf.default.max_addresses = 1 # Optimization for port usefor LBs # Increase system file descriptor limit fs.file-max = 65535 # Allow for more PIDs (to reduce rollover problems); may break some programs 32768 kernel.pid_max = 65536 # Increase system IP port limits net.ipv4.ip_local_port_range = 2000 65000 # Increase TCP max buffer size setable using setsockopt() net.ipv4.tcp_rmem = 4096 87380 8388608 net.ipv4.tcp_wmem = 4096 87380 8388608 # Increase Linux auto tuning TCP buffer limits # min, default, and max number of bytes to use # set max to at least 4MB, or higher if you use very high BDP paths # Tcp Windows etc net.core.rmem_max = 8388608 net.core.wmem_max = 8388608 net.core.netdev_max_backlog = 5000 net.ipv4.tcp_window_scaling = 1
Load the directives to memory using the following command:
# sysctl -p
Check the output for any error messages — correct those, reload — and done.
The next phase is getting the server ready to install the LEMP stack
Fetching Nginx, PHP-FPM, APC & MySQL
The default Debian repo contain older versions of Nginx and MySQL. The excellent maintainer at DotDeb offers a Debian 6.0-compatible source with newer packages of these servers.
/etc/apt/sources.list file, and append the following statements to setup the repo:
#Dotdeb repo deb http://packages.dotdeb.org stable all deb-src http://packages.dotdeb.org stable all
Next up, import the signing key to apt as follows:
# wget http://www.dotdeb.org/dotdeb.gpg # cat dotdeb.gpg | apt-key add -
Time to install Nginx, MySQL, PHP-FPM and other essential packages:
# apt-get install nginx-full # apt-get install mysql-server-5.5 # apt-get install php5-fpm php5-mysql php5-apc php5-xsl php5-xmlrpc php5-sqlite php5-curl php5-gd php5-tidy
While you’re on an
apt-get mood, go ahead and install the following two utilities as well:
# apt-get install rsync htop
rsync will help us later to fetch the WordPress files from the current server, while
htop is a personal choice over the
top command — just looks a bit better :-)
A memory consumption of 114MB after setting up the WordPress site, and right after reloading
mysql services (more about that later) is impressive.
By the way, we haven’t set the server timezone yet, have we? Run the following to set it to your local time:
# dpkg-reconfigure tzdata
I, of course, have set it to IST (Asia/Kolkata — UTC + 0530Hrs).
That’s it for today. In the second part, which hopefully should be up by Monday, we’ll cover how to setup a Nginx vhost for WordPress, move the files from the old server to the new one, import the MySQL database, and finally make necessary changes to the CDN facility and CloudFlare’s DNS.
For the time being, enter your VPS IP address in your browser and you should see the Nginx server there, for now :-)