WordPress on Heroku


With 18% of the internet running off WordPress and between 50–100k sites launching on it every day using WordPress seemed the obvious choice. Where to host it however, was a more difficult decision. With such a large market share comes a myriad of deployment options. From large turnkey blog as a service providers like WordPress.com and WP Engine to complete roll your own WordPress.org on AnyHostUSA. In the end I decided to go with a happy median, running WordPress on a PaaS cloud provider (I hear it’s web scale that way) for the extra flexibility. This also has the benefit of giving me an excuse to learn more about the internals of cloud services.

Heroku

Being one of the first PaaS providers Heroku is one of the largest. Originally a RoR provider they have expanded to many other languages including PHP, which is as of yet undocumented. A quick google search reveled that there are already others running with this setup and there’s even a repo for it on GitHub. True to the promise of the cloud, a couple of minutes and a handful of commands later I saw the following scroll by my terminal.

-----> PHP app detected
-----> Bundling Apache version 2.2.22
-----> Bundling PHP version 5.3.10
-----> Discovering process types
Procfile declares types -> (none)
Default types for PHP -> web

-----> Compiled slug size: 14.2MB
-----> Launching... done, v6
http://XXXXX.herokuapp.com deployed to Heroku

A single git push and a full LAMP stack gets bootstrapped & starts serving this very WordPress site within seconds. It’s the future!

Trouble in Paradise

One of the first plugins I wanted to install was Jetpack which bundles a bunch of neat features together. However just installing and pushing to production proved fruitless. While the plugin showed up in the control panel correctly, enabling it and trying to connecting to a WordPress.com account would always produce a register_http_request_failed error. Checking the Heroku access logs showed the following.

2013-06-03T13:15:21.391493+00:00 heroku[router]:
    at=info
    method=HEAD
    path=/wp-admin/admin.php?page=jetpack&action=register&_wpnonce=XXXXXXXXXX
    host=www.xyu.io
    fwd="24.61.130.200"
    dyno=web.1
    connect=1ms
    service=16488ms
    status=200
    bytes=0
2013-06-03T13:15:21.511473+00:00 heroku[router]:
    at=info
    method=POST
    path=/xmlrpc.php?for=jetpack
    host=www.xyu.io
    fwd="72.232.7.14"
    dyno=web.1
    connect=13ms
    service=15066ms
    status=200
    bytes=207
2013-06-03T13:15:21.810721+00:00 app[web.1]:
    10.68.182.136 - - [03/Jun/2013:13:15:04 +0000]
    "HEAD /wp-admin/admin.php?page=jetpack&action=register&_wpnonce=XXXXXXXXXX HTTP/1.1"
    200 -
2013-06-03T13:15:21.810721+00:00 app[web.1]:
    10.241.82.47 - - [03/Jun/2013:13:15:21 +0000]
    "POST /xmlrpc.php?for=jetpack HTTP/1.1"
    200 207

It appears the connect to WordPress.com request triggers a XML-RPC callback from the Jetpack servers to complete the setup. However Heroku web dynos are by default single threaded and the initial connect request is blocking the callback from executing.

With each dyno being allocated 512MB of memory this hard limit seemed very weird but the httpd.conf on the dynos indeed showed

ServerLimit 1
MaxClients 1

This seems like a highly inefficient allocation of resources, spinning up an entire stack for a single Apache child process. Luckily, Heroku allows custom bootstrapping of dynos via a Procfile. Using this method I added an include to the end of httpd.conf to load app specific overrides, allowing in my case up to 8 concurrent connections.

Securing the Server

Normally all files in the repo pushed will live in /app/www/ which is also the DocumentRoot. This in effect makes all files in the repo web accessible unless protected via an .htaccess file. Although there is nothing too sensitive being pushed I would rather not expose all my config files. Given that we are already parsing an app specific httpd.conf file it was a no brainer to move web files to an /app/www/htdocs/ subdirectory and set the DocumentRoot accordingly.

Giving Back

With these changes in place Jetpack now connects properly and the site is a bit snappier due to the concurrent connections to boot. I’ve pushed my branch to GitHub and initiated a pull request perhaps others will also find it helpful.