WordPress on Nginx, Part 1: Preparing VPS the Debian Way

Preparing a Debian-based LEMP server
Preparing a Debian-based LEMP server

Preparing a Debian-based LEMP server

In this first part, we deal with the LEMP (Linux, Nginx, MySQL, PHP) stack recipe and set up the basic server after installing the required packages.

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:

PermitRootLogin yes

…and change this to read:

PermitRootLogin no

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:

/etc/init.d/ssh restart

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.

Open your /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 :-)

htop screenshot -- note the memory consumption
htop screenshot — note the memory consumption

A memory consumption of 114MB after setting up the WordPress site, and right after reloading nginx, php-fpm and 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
The ncurses-based interface makes setting up server time a no-brainer
The ncurses-based interface makes setting up server time a no-brainer

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 :-)

Nginx server is listening...
Nginx server is listening…


  1. […] the Web server is NginxIf you’re on a vps, or dedicated server, chances are you’re running nginx as a web server instead of Apache (like we are). To restrict WP logins to a specific IP address add the following location blocks to […]

  2. Good article on how to set this up on debian I use both debian and centos. Mostly debian for my fusionpbx installs and centos for my lemp installs. You should take a look at the centminmod script for installing LEMP it is pretty awesome http://centminmod.com/

    I would also like to thank you for the securing wp-admin article learned a few things I didn’t know. Also a few vps providers that I can recommend on the cheap but good side. Buyvm.net, prometeus.net, and ramnode.com. Anyways keep up the good work.


Please enter your comment!
Please enter your name here