In the past I’ve written about running WordPress more efficiently [on Apache](http://joshbetz.com/2012/01/wordpress-low-memory/). Part of that was using Nginx to serve static files and only relying on Apache to interact with PHP. But we don’t need Apache for that. We can achieve a similar result with PHP-FPM and never have to install Apache.
Recently I rebuilt my server on a the [Rackspace Cloud](http://www.rackspace.com/cloud/public/servers/). Here I’ll detail the steps involved in setting up the server from the start.
Note: I’m using Ubuntu 12.04.
#1. Initial Setup
When your server first starts up, the only user you can access is root. Rackspace will send you an email with the root password. The first thing you need to do is change that. This should be something that you have to open 1Password for every time, and definitely not “123456”.
passwd
Then I like to add a non-root user for myself. You can add this user to the admin group, which automatically gives you sudoers access, but I’d recommend against it.[^sudosecurity] Anything that needs root privilege can be done with the root user. In fact, you probably want to stay logged in as root while we finish installing the services.
useradd -d /home/myuser -m myuser
passwd myuser
If you want to add your user to the admin group.
groupadd admin
usermod myuser -G admin
Next, you probably want to install any updates that are available.
apt-get update && apt-get upgrade
As a final step in the setup, I like to grab [my dotfiles](http://github.com/joshbetz/dotfiles) from Github so my shell is useable.
#2. Uncomplicated Firewall
Also in the name of security, a software firewall. Realistically, you probably only need to allow the outside world to talk to your server on port 22 (for ssh), port 80 (for http), and *maybe* port 443 (for ssl).
ufw default deny incoming
ufw default allow outgoing
ufw allow ssh
ufw allow www
ufw allow 443
ufw enable
#3. Nginx, PHP-FPM, and MySQL
Let’s install a web server on this web server. I found [a great article](http://www.howtoforge.com/installing-nginx-with-php5-and-php-fpm-and-mysql-support-lemp-on-ubuntu-12.04-lts) that walks through setting up Nginx on Ubuntu 12.04. Honestly, after you do it a couple times, this becomes really easy. Nginx config files are generally pretty easy to read.
apt-get install mysql-server mysql-client
apt-get install nginx
service nginx start
apt-get install php5-fpm
I’m going to set up a default virtual host as a wild card to grab any request that isn’t to my domain and redirect it.
vim /etc/nginx/sites-available/default
server {
server_name _;
rewrite ^ $scheme://joshbetz.com$request_uri redirect;
location = /50x.html {
root /usr/share/nginx/www;
}
}
Then we can set up the virtual host for our specific domain. The article I linked to does this a little differently. I’m going to do some WordPress specific things right away, but you could set it up generically and then come back to this if you wanted to. The idea here is to put the rules specific to this site in a this virtual host and then have some generic files we include incase we want to set up another WordPress site on this server. Honestly, you could simplify this into one file if you’re only ever going to set up one site.
First open `/etc/nginx/sites-available/mysite.com` with vim and paste the following, editing the relevent bits.
server {
listen 80; ## listen for ipv4; this line is default and implied
listen [::]:80 default ipv6only=on; ## listen for ipv6
root /var/www/joshbetz.com;
index index.php index.html index.htm;
# Make site accessible from http://joshbetz.com/
server_name joshbetz.com jbe.me;
include global/restrictions.conf;
# More rules here
include global/wordpress.conf;
}
Next up is `/etc/nginx/global/restrictions.conf`.
# Global restrictions configuration file.
# Designed to be included in any server {} block.</p>
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;
}
And finally, `/etc/nginx/global/wordpress.conf`.
# WordPress single blog rules
# Designed to be included in any server {} block.
# This order might seem weird – this is attempted to match last if rules below fail.
# http://wiki.nginx.org/HttpCoreModule
location / {
try_files $uri $uri/ /index.php?$args;
}
# Add trailing slash to */wp-admin requests.
rewrite /wp-admin$ $scheme://$host$uri/ permanent;
# Directives to send expires headers and turn off 404 error logging.
location ~* .(?:js|css|png|jpg|jpeg|gif|ico)$ {
expires max;
log_not_found off;
}
# Uncomment one of the lines below for the appropriate caching plugin (if used).
#include global/wordpress-wp-super-cache.conf;
#include global/wordpress-w3-total-cache.conf;
# Pass all .php files onto a php-fpm/php-fcgi server.
location ~ .php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+.php)(/.+)$;
fastcgi_pass unix:/tmp/php5-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
}
Now we need to configure PHP-FPM to be compatible with the Nginx configuration. Comment out the `listen` directive and add a new one like the one below or just replace the localhost address with `/tmp/php5-fpm.sock`. Open `/etc/php5/fpm/pool.d/www.conf` and looks for something like like `listen = 127.0.0.1:9000` and replace it with the following.
;listen = 127.0.0.1:9000
listen = /tmp/php5-fpm.sock
Then restart PHP-FPM with `service php5-fpm reload`. Time to run the WordPress install!
#4. Postfix
WordPress needs to be able to send email for certain things. We’ll use Postfix for this.
apt-get install postfix
I was having a problem because the hostname of my server is joshbetz.com, but this isn’t actually a mail server. In `/etc/postfix/main.cf`, there’s a line: `mydestination`. It was automatically set to “joshbetz.com, localhost.com, localhost” causing postfix to intercept messages to joshbetz.com before they ever left the server. I just removed “joshbetz.com” from that list and restarted postfix to fix the problem.
#5. Alternative PHP Cache
APC is a PHP opcode cache — it makes PHP faster. But it also has a key-value store that can be used for the [WordPress Object Cache](http://codex.wordpress.org/Class_Reference/WP_Object_Cache). Mark Jaquith has [a nice plugin](http://wordpress.org/extend/plugins/apc/) for this.
apt-get install php-apc
#6. iStat
You might know iStat for the Mac dashboard widget or the [iPhone app](http://bjango.com/iphone/istat/). There’s also a version of [iStat server for Linux](https://github.com/tiwilliam/istatd). The setup is a bit outside the scope of this article, but there are pretty good instructions in the readme on Github. Once it’s setup, you can access information about your server like CPU load, free memory, and disk utilization all from you iPhone.
#7. cdn
So, I have a love/hate relationship with Rackspace’s Cloud Files. This is the only part that I haven’t actually finished yet because I can’t decide if I want to use Cloud Files or Amazon’s CloudFront — or just skip the CDN aspect all together for now.
Origin pull would be really nice — if a file doesn’t exist on one of the edges, it just gets pulled from your server. Both Rackspace and Amazon claim to have origin pull. Amazon’s origin pull works exactly the way it’s supposed to. You have to think a little differently to understand Rackspace’s approach though.
First, you have to consider Cloud Files not as a CDN[^notacdn], but just a place to store your files — a file server if you like. You can then tell Cloud Files to turn on the CDN, which hooks it into Akamai’s CDN. From the store on Cloud Files, there is indeed origin pull, but Cloud Files has no way of asking your Cloud Server for a file if it doesn’t exist.
#VirtualBox
I used this as an opportunity to rebuild my virtual environment as well. I’ve written a couple of posts about virtualizing your local development environment in the past. Turns out, VirtualBox does some weird stuff to shared files sometimes.
The new server was killing some of my javascript and adding null bytes to the end. I found [an article on serverfault](http://serverfault.com/questions/401081/nginx-serves-broken-characters-nginx-on-linux-as-guest-system-in-vbox) that addressed this issue. If you turn sendfile off in nginx, it fixes the problem. Apparently it can happen with Apache too, but this is the first time I’ve seen it.
#Further Reading
* [Set up VirtualBox for Web Development](http://joshbetz.com/2012/01/set-up-virtualbox-for-web-development/) – An article I wrote about virtualizing your development environment in VirtualBox.
* [Virtualized Development, Part 2](http://joshbetz.com/2012/03/virtualized-development-part-2/) – A follow up article I wrote about sharing files between a VirtualBox host and client. With a similar setup, I can edit all my files in OSX, but use Ubuntu to serve them on a local domain.
* [http://codex.wordpress.org/Nginx](http://codex.wordpress.org/Nginx) – The codex entry on WordPress.org has some good stuff.
[^sudosecurity]: My recommendations about security and what you actually do in practice might not be the same. Be aware of the risks though — especially if your account has a password that might be easily cracked.
[^notacdn]: Because Cloud Files isn’t a CDN. It’s a file server that happens to have the ability to hook into Akamai’s CDN if you want it to.