29 August 2008

WordPress, Nginx, Subdirectories, and Separate Server Proxying

Ok, first, apologies for the title of this entry, there wasn't exactly a concise way to title what this is about :) So, here's a better description of what this entry is about...

I needed to setup a WordPress blog for a client, but they needed the blog to be located as a subdirectory of their primary domain name, as opposed to its own domain name or a subdomain. Furthermore, we use Nginx for all our web servers, and so needed to be able to configure Nginx both on the main domain name serving infrastructure, as well as the machine that would host the blog. There were some other writeups on the web, but none quite covered this case - basically the complication of proxying to it, as well as WordPress being in a "subdirectory" in terms of the path. So, here's how I got it working.

But first, a quick intermission. This was made possible by the fine support folks at EngineYard, in particular David S, but others as well. They provided the solution to the final problem we ran into, as well as a few tips along the way. Our primary servers and testing/staging systems are hosted at EngineYard, but the WordPress blog is actually on a Slicehost slice, so double thanks to them, as even though part of it involved their systems, part did not. As so many others have said, EngineYard is really superb, and absolutely worth the price. Getting on with it...

I'm going to do this not precisely in the order I did it, but the idea is to cut out my missteps and just give you (and me for future reference) a recipe for this.

Setup the Wordpress Server

For me this was an Ubuntu 8/Hardy machine, using MySQL as the DB, and WordPress 2.6.1, with Nginx as the web server. Presuming you have a base Ubuntu install, you may or may not need all these steps (e.g. you may already have PHP):

Install PHP CGI, and PHP MySQL components:

sudo aptitude install php5-cgi php5-mysql

Install Lighttpd to get the spawn-fcgi program that will take care of handling PHP FastCGI requests, but then stop Lighttpd and remove its daemon service:

sudo aptitude install lighttpd
sudo /etc/init.d/lighttpd stop
sudo rm /etc/init.d/lighttpd
sudo update-rc.d lighttpd remove

Next, setup the spawn-fcgi program to run as a daemon. You need to pick a port you'll use, in my case I happened to choose 53987 (at random). The following daemon script should be placed in /etc/init.d/spawn-fcgi or a name you like. Note that this is somewhat specific to Ubuntu, but likely works on similar distros or can be adapted for RedHat-based ones, etc.:

#! /bin/sh

# Provides: nginx
# Required-Start: $all
# Required-Stop: $all
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: starts FastCGI for PHP
# Description: starts FastCGI for PHP using start-stop-daemon

DAEMON_OPTS="-f /usr/bin/php-cgi -a -p 53987"

test -x $DAEMON || exit 0

set -e

case "$1" in
echo -n "Starting $DESC: "
start-stop-daemon --start --quiet --pidfile /var/run/$NAME.pid \
echo "$NAME."
echo -n "Stopping $DESC: "
start-stop-daemon --stop --quiet --pidfile /var/run/$NAME.pid \
--exec $DAEMON
echo "$NAME."
echo -n "Restarting $DESC: "
start-stop-daemon --stop --quiet --pidfile \
/var/run/$NAME.pid --exec $DAEMON
sleep 1
start-stop-daemon --start --quiet --pidfile \
/var/run/$NAME.pid --exec $DAEMON -- $DAEMON_OPTS
echo "$NAME."
echo -n "Reloading $DESC configuration: "
start-stop-daemon --stop --signal HUP --quiet --pidfile /var/run/$NAME.pid \
--exec $DAEMON
echo "$NAME."
echo "Usage: $N {start|stop|restart|reload|force-reload}" >&2
exit 1

exit 0

You can download/get the above script here.

Then make it a daemon by doing:

cd /etc/init.d
sudo update-rc.d spawn-fcgi defaults

Now install the WordPress files and setup the database

Download the latest tarball, and unpack it where you want it to be. In my case, this was in /var/www/wordpress/blog. Note the sort of double directory there. That is because the subdirectory within the path that the blog is accessible at is "blog", i.e. http://www.example.com/blog. The key here is that the directory name that WordPress ultimately lives in, needs to be the same as the subdirectory in the path of the URL.

Follow steps 1-5 of the WordPress install guide, where you setup the database, etc.

Install and Configure Nginx:

sudo aptitude install nginx

My configuration file for Nginx, which lives in /etc/nginx/sites-available/blog, and is symlinked from /etc/nginx/sites-enabled/blog, looks like this:

server {
listen 80;
server_name blog.example.com;

root /var/www/wordpress;
index index.php;

access_log /var/log/nginx/blog.access.log;
error_log /var/log/nginx/blog.error.log notice;

location / {
root /var/www/wordpress;
index index.php;

# this serves static files that exist without running other rewrite tests
if (-f $request_filename) {
expires 30d;

# this sends all non-existing file or directory requests to index.php
if (!-e $request_filename) {
rewrite ^(.+)$ /index.php?q=$1 last;

location ~ .php$ {
include /etc/nginx/fastcgi_params;
fastcgi_index index.php;

fastcgi_param SCRIPT_FILENAME /var/www/wordpress$fastcgi_script_name;

root /var/www/wordpress;

You can download this file here.

In the above configuration file, if you didn't locate your WordPress install at /var/www/wordpress/blog, then you need to edit the various /var/www/wordpress paths you find. Also, adjust the port number as needed, and any of the paths to log files, etc.

To explain this config file, you have an initial location directive that has two parts within it. The first says that if the request is for a static file, just serve it up and ignore any further rules. The second says that if it doesn't find a static file, then rewrite it to an index.php based URL, and continue. This leads to the second location directive which processes PHP requests, sending them to the fastcgi processor, which is setup via the SCRIPT_FILENAME setting, as well as the common settings in the included fastcgi_params (which is part of a base Nginx install).

WordPress Install and Testing

Now we can fire everything up on the WordPress server, get it installed (WordPress wise), and make sure it's working, before moving on to setting up the proxying from the other server that handles the domain. The below depends some on how you access your WordPress server directly (e.g. via IP or subdomain, etc.). I was doing it by IP while waiting for the DNS for the blog.example.com part to be working.

Start up the spawn-fcgi, and Nginx daemons:

sudo /etc/init.d/spawn-fcgi start
sudo /etc/init.d/nginx start

Proxy/Domain Server Setup

On the server(s) that host your domain, you need to setup the proxying in Nginx to send requests to the /blog path over to your WordPress server. To do that, I added the following to my Nginx configuration:

location /blog {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect false;
proxy_pass http://;

This file is availble here.

Note that you need to insert the IP Address of your WordPress server where indicated (or use the subdomain - I don't have that going yet, as I'm still waiting for my subdomain DNS to percolate through).

WordPress Installer and Config

Now surf over to the WordPress installer at:

http://IP ADDRESS/blog/wp-admin/install.php

This should fire up the WordPress installer and off you go. Now, big caveat here, as this isn't quite how I did it, so I can't say for certain that this install phase will work properly. To diverge for a minute, incase this happens to you, I initially had WordPress installed directly in /var/www/wordpress. I then hit http://IP ADDRESS/wp-admin/install.php, and did the install. Once installed, I went into the "Settings" tab of WordPress's admin area, and changed the site URL and home page URL's to be http://www.example.com/blog. And then, I moved the actual WordPress files from /var/www/wordpress to /var/www/wordpress/blog.

Back to the suspect way to use... You will want to ensure in the Settings panel of the WP admin to set your blog URL's (site URL and homepage) to be http://www.example.com/blog. This is so that WordPress will generate URL's that look like that (instead of looking like one with the IP address in it, or however you originally accessed the installer).


Anonymous said...

Chris, did you ever try to integrate the authentication of the remote WordPress installation with the rails app? I'm looking at doing pretty much the same thing (EY rails app, WP blog hosted externally but in namespace of rails app) but I'm not thrilled with the prospect of requiring a second Userid/password for my users -the blog and the rails app are to be very tightly linked and two auth systems smells.

Chris said...

DealBase doesn't have any authentication in it, so I haven't played with that. You're basically after Single Sign On right? Since we handle all this at the Nginx level, it wouldn't be do-able with that setup. But, what I could see (I'm theorizing) is that you actually create a Rails route for "/blog", and if the user going to that has authenticated in your Rails app, you could pass that info on to Wordpress via headers, or even have your controller action that handles the /blog route do a login form post on Wordpress (but this wouldn't necessarily handle then taking the user to a particular requested blog page, etc.). Maybe there are SSO modules for WordPress you could integrate?