Runbook for nginx/php-fpm/MySQL stack for WordPress

If you have a message to get out to the word there’s never been an easier way to get it out there than a blog. (I think the last innovation was mailing around handbound, Xeroxed, “books” or “xines”.) This is also cheaper (well your costs will increase with your readers, but you should be able a way to cover your costs if you have the traffic.)

I would never recommend PHP for anything, anymore, but running an old (and therefore much tested) version of PHP5 with MySQL leads to a pretty straightforward, secure, easily scalable, experience for most new sites. I can’t think of a cause, business, or organization that wouldn’t benefit from the blog channel to communicate a lot of information to the public. So much to spend money on in a new organization, not much need for creating something more complex unless you have a specific requirement.

I have looked into WordPress sold as a service with idea that I wouldn’t need to worry about maintenance and scalability, but I found usability to be not much improved over a humble EC2 instance, and I haven’t found a better/cheaper way to distribute static content than S3.

Every app store needs a support URL, and the most straightforward way to provide that is a WordPress blog.

At the very least your blog for an app should tell the visitor what the app does, and contain instructions on situations the user might encounter. This alone will reduce your support e-mails, so as are most things, this is easier to handle preemptively.

Step 1: Register domain

Not to be thought about but to be done. Aggressive competition has put pricing in the same ballpark. I used to use GoDaddy and had no real issues with them lots of ad’s and confusion (for a while I was having to comb through my e-mail to look for my customer number to login) now I use NameCheap, offers pretty much the same services with a cleaner interface. Would suggest private registration unless you want telemarketers calling you.

Update 2018

Due unilateral action by Google, you pretty much have to get a SSL certificate now.

First about OpenSSL certificates they are tricky:
Security certificates are tricky to shop for because a lot of people don’t know what exactly what they are. Anyone can create an SSL certificate, what you’re paying for is the certificate authority to vouch for your identity (ties you to that IP). It’s kind of a racket like a lot of the security industry. I think VeriSign charges like $5,000/yr and they require you to maintain an office they can visit during business hours.
These two are pretty decent:
That one is low end, you’re probably going to be using an automated system to validate at that price.
This is really good deal:
This one has organizational validation, so it’s going to be more trouble. At the bottom it describes what they require, but the thing I like about this they guarantee two business day turnaround. The good thing about organizational validation is you get a $100,000 in insurance, this will give us access to better options in payment processing. Even PayPal might require it if we do enough volume.
I would go to namecheap (or any registrar, most are more expensive, they all work the same, GoDaddy has more ads) pick out a domain you can get cheap (don’t bother with anonymous WhoIS, not going to work for SSL in many cases, just get a PO BOX).
Step 2: Launch Instance

These instructions will work with any VPS/dedicated server running RHEL/CentOS/Amazon AMI but were designed around Amazon Linux. Somewhat recently Amazon has added a ‘nano’ VM, which would seem to fit the bill for this type of site with perhaps 2 legitimate users concurrently using the site at the same time. Problem with this is early on you will be set upon by foreign botnets trying to sell you SEO services as you anxiously watch your Google Analytics stats those first few days…

I haven’t had much luck with the nano instances and MySQL and independently of that I hadn’t had much luck with SQLite3 and Drupal with almost no traffic. The ‘micro’ instance is the best choice for the lack of headaches/cost ratio. I think it’s free for a while if you’re a new user.

EC2 instances are created in a “Wizard” type user interface for those of you that remember Windows 98. Most important default setting you need to check is “Auto-assign external IP”. The cynical part of me thinks this is because they want you to think you need an Elastic Load Balancer service long before you actually do. Everything else should be OK by default. One thing that might surprise a new user to AWS is EC2 instances are only accessible by key files. The Wizard does a pretty good job of explaining the implications of this these days.

Your instance will take a few minutes to spool up, so a good thing to do is update the security groups to accept inbound traffic on port 80. (Port 22 is enabled by default, you will use that port to manage your server and port 80 to access it)AWS Security groups

When your new instance is green on the dashboard, time to login, replace key file with wherever you saved your keyfile and ecwhatever with your public amazon DNS or even your public IP address:

ssh -i keyfile.pem ec2-user@ecwhatever.amazonaws.com

The Amazon AMI convention is that every instance starts out with a user ec2-user which can sudo (perform commands as root). When a command is prefaced with a # instead of a $, that means you must perform the command as root, i.e. prepend sudo to the command.
When the connection is successfully, you will be greeted by an ASCII art rendition of Amazon AMI, and possibly a warning that few packages are out of date, you might as well update them now, it’s the safest time possible.

Step 3: Install nginx
# yum update
# yum install nginx
# service nginx start
# chkconfig nginx on

What these commands just did was to install nginx, start the service and ensure the service will automatically start on a reboot. (I’ve never heard of anyone ‘losing’ data from AWS but random reboots happen for whatever reason, this will save you a huge headache over starting the service yourself, usually prompted by a customer complaint)

If everything went correctly when you enter your publicly accessible DNS/IP address you should get:
nginx default page

Now you won’t be able to install WordPress gracefully until your new domain resolves to this test page, so take a minute to login to the Advanced DNS section if your using NameCheap. They should have helpfully already have the domains pointed to sites hosting advertisements they get money from. Make an A record with a ‘host’ of ‘@’ and the IP as your publicly available IP address.

Advanced DNS settings

It will take some indeterminate amount of time for your new domain to resolve your instance because there is DNS caching all the way down to your browser. I find a private browsing window on Safari to catch changes in DNS records far faster than Chrome but all should be updated in time. There is still some work to do on the server anyway.

UPDAte 5: Activate SSL Certificate (update 2018)

Now that your nginx instance is up, the easiest way to do this is to login and a SSL certificate. To prove you control the server you have several methods, one is e-mail which I don’t like to enable on web servers for various reasons, the other is posting a file the SSL provider gives you to post in a location on your server. Much easier to do this without the app installed, just as a simple static website.

Login to your new server instance and execute the following command.

openssl req 
       -newkey rsa:2048 -nodes -keyout domain.key \
       -out domain.csr

You will need to provide this to your SSL provider to activate your certificate. A note on activation, the SSL certificate is tied to your IP, it would be best to make this a load balancer or at least some sort of elastic IP.

Depending on how your SSL certificate provider does things, you should get a a certificate file.

If you are using a load balancer you can just load your key and certificate file you received from the registrar and just use standard HTTP for the actual servers, this will save a lot CPU usage. Think about if a server goes down, it will be a lot quicker to get up (with SSL security) if you use have the SSL cert tied to a (possibly virtual) load balancer instead  of a single server.

If you are using a single web server, follow the database’s documentation on where to place your private-key and verified certificate files.

Step 6: Install MYSQL

MySQL is the humble, free RDBMS that brought you Twitter, Facebook, etc. all those years ago. Lot’s of good documentation on scaling available as well as consultants.

Especially with MySQL 5.6 which comes with performance schema enabled, by default (which requires 400MB+ RAM minimum and not very useful on smaller sites where that is a lot of memory), but you can save a lot on hosting by changing some defaults in my.conf

# saves like 400MB RAM (only in MySQL 5.6+)
performance_schema=off
# saves more RAM on small sites
key_buffer_size = 16K
max_allowed_packet = 1M
table_open_cache = 4
sort_buffer_size = 64K
net_buffer_length = 8K
read_buffer_size = 256K
read_rnd_buffer_size = 512K

Note that the directions change slightly for every Linux distribution for Ubuntu or Debian, you would use apt-get instead of yum, etc. The example below is AWS-Linux (sort of like CentOS).

# yum install mysql-server
# sudo service mysql-serv start
# sudo chkconfig servce on

MySQL 5.5 is the latest version of MySQL that is easy to install (this is complicated enough as it is) with php.

$ mysql -u root
CREATE DATABASE wordpress;
exit

Now strictly speaking some geek would tell you to put some passwords on the root user and run wordpress as a sub user. I don’t think it’s worth the while until you get enough traffic that you need an external MySQL instance. For giving information about a new app, this is usually not the case. Otherwise lot’s of consultants are available to help you scale out. We are just going to assume you are blocking external connections on port 3306. We are going to be really time-efficient and use root as the database owner with no password and wordpress as the default database since this little instance isn’t going to be doing anything else.

Step 6: Installing PHP-FPM

A few years ago, WordPress was still the best/easiest/ubiquitous/cost-effective way to promote a new product on the internet, but the choice of web server was Apache with mod_php. As with anything there are probably tradeoffs with both but the world seems to be switching to nginx which began as a more primitive web server (sometimes referred to as a reverse proxy). Probably the most complicated part of this process is setting up FastCGI.

First we need to install php, php-fpm and php-mysql.

# yum install php php-fpm php-mysql

We’re going to make this a little more complicated/efficient by using a Unix pipe instead of TCP/IP packets. (this way we avoid the useless overhead of the TCP/IP stack, when we don’t need it) A bit of headroom from your worst nightmare, the “Cannot connect to database” error when your database runs out of memory from a sudden ad-bot attack.

Changes to: /etc/php-fpm.d/www.conf

listen = /var/run/php5-fpm.sock
; ** IMPORTANT **
; Set permissions for unix socket, if one is used. In Linux, read/write
; permissions must be set in order to allow connections from a web server. Many
; BSD-derived systems allow connections regardless of permissions.
; Default Values: user and group are set as the running user
;                 mode is set to 0666
listen.owner = nobody
listen.group = nobody
listen.mode = 0666 

Changes to /etc/nginx/nginx.conf

index   index.html index.php;

server {
        listen       80 default_server;
        listen       [::]:80 default_server;
        server_name  localhost;
        root         /usr/share/nginx/html/wordpress;
        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        location / {
; mod rewrite for WordPress
                try_files $uri $uri/ /index.php?$args;
        }
	location ~ \.php$ {
            root           html;
            fastcgi_pass   unix:/var/run/php5-fpm.sock;
            fastcgi_index  index.php;
            fastcgi_param  SCRIPT_FILENAME  /usr/share/nginx/html/wordpress$fastcgi_script_name;
            include        fastcgi_params;
        }
}

Now at least in Amazon AMI, nginx’s default root location is /usr/share/nginx/html. I added wordpress since when you download the latest from wordpress it will expand the tarball into a folder named wordpress.

A good test to see if everything is correct up to now is is to create a folder ‘wordpress’ and a include a file ‘test.php’

<?php
phpinfo();

Now when you navigate to your-new-domain.com/test.php you should see all there is to need to know about your PHP installation. Most important thing is that the MySQL connector is installed and working.

Now that we’re done with the difficult part a few more things need to be done:

# service nginx restart
# service php-fpm start
# chkconfig php-fpm on
$ cd /usr/share/nginx/html
# wget https://wordpress.org/latest.tar.gz
# gzip -d latest.tar.gz
# tar -xvf latest.tar
$ cd wordpress
# chmod a+w wp-content

All of this should set you up for a brand new WordPress blog install that will allow file uploads. When you navigate to http://mydomain.com you should get a WordPress install prompt. You will have to create the wp-config.php manually although the contents will be supplied… after that all management should be through the control panel at http://mydomain.com/wp-admin/