Serverless WordPress on AWS Lambda

There are a few ways to run WordPress “serverless” on AWS. I’m going to talk about running WordPress on Lambda for this article. If you’re interested in how you can run WordPress serverless-ly on Fargate, I’m working on a post about that too.

Keep in mind that while it is possible to do this, it’s not for everyone. It’s probably not for me. Probably not for you. Use at your own risk!

Before we start, there is a core feature of Lambda that make running WordPress in Lambda quite troublesome: Read-only file system. WordPress expects a writable, persistent, local file system. We’ll be using the S3 Uploads plugin by Human Made to handle media uploads. However, core and plugin updates will not work. There’s no workaround for this, so to install / update files, we’ll need to make a new Lambda deployment.

So: let’s go! First, you’ll want to clone my boilerplate repository. I’ve prepared a WordPress installation and a simple glue script to actually boot WordPress.

$ git clone https://github.com/keichan34/wordpress-on-lambda

My plan of attack is: run WordPress in the Lambda function using a PHP custom runtime, make uploads work with S3 instead of the local filesystem, and wire up the database. In the repository above, I’ve configured static assets to be served from S3 as well.

Now, let’s prepare the database. Lambda has two networking modes: public and VPC mode. In public mode, the Lambda has default access to the public internet, but nothing else. In VPC mode, the Lambda is booted inside the VPC, and doesn’t have public internet access by default. Because WordPress requires public internet access we have to either run it in public mode, or run it in VPC mode and prepare a NAT gateway (about $30 to $50 a month, depending on the region). If Lambda runs in public mode, the database must also be publicly accessible — something that is frowned upon from a security standpoint. You should choose the option that fits your risk and price profile. In my case, I’m going with the NAT gateway route.

Now we’ve got the messy stuff out of the way, we’ll have to assemble the Lambda runtime. AWS has an article on their blog detailing how to make a PHP custom runtime, but Stackery provides a batteries-included PHP layer. It includes everything you need to make a PHP application that assumes it’s running in a traditional server environment run in AWS Lambda.

# Replace "km-wordpress-on-lambda-deployment-201906" with something that makes sense for you. It's globally unique, so copying and pasting this will result in an error.
# Make sure you're in the same region as your database!

$ DEPLOY_BUCKET="km-wordpress-on-lambda-deployment-201906"
$ aws s3 mb "s3://$DEPLOY_BUCKET"
$ cd <the directory you cloned the GitHub repository to>

Now, it’s time to install WordPress! We’ll add the WordPress files to the deployment package. As usual, copy wp-config-example.php to wp-config.php. Enter your database details. If you have a hostname that you’re going to use with CloudFront, enter it now. If not, you’ll have to wait until after the CloudFront distribution is created, then try again.

Now, let’s deploy. This will create a new CloudFront distribution and S3 bucket for public assets, so maybe it’s a good time to make a cup of coffee. If you haven’t installed the SAM CLI, do that before the next block.

$ sam package --template-file template.yaml --output-template-file serverless-output.yaml --s3-bucket "$DEPLOY_BUCKET"
$ sam deploy --template-file serverless-output.yaml --stack-name wordpress-on-lambda --capabilities CAPABILITY_IAM
$ aws s3 sync ./src/php s3://deploy-bucket-XXXXX --exclude "*.php" --exclude "*.ini"

I’ll be using the default CloudFront domain for this demo. If you’re going to be using your own domain, you need to modify the template.yaml file to add the an alias to the CloudFront distribution. Use the following command to show the CloudFront domain name.

$ aws cloudformation describe-stacks --stack-name wordpress-on-lambda | jq '.Stacks[0].Outputs'

OK! Now, you should be able to access the CloudFront URL, and you’ll get redirected to the friendly WordPress installer! If you’ve set up your wp-config.php correctly, the installation should go smoothly.

The site I set up for this post is available here: https://dskhgdbzphjkm.cloudfront.net/

Lessons Learned

This is for almost no-one. I think the only valid use case (in this current form) for running WordPress in AWS Lambda is a site that gets periodic, unpredictable spikes of intense traffic — a use case where Lambda’s scalability and price model pays off. This is also a use case where, presumably, the benefits of the scalability trumps the inconvenience of not being able to use the online updaters and installers (also, I’m assuming the database will be able to keep up with the load).

However, if updating and installing themes or plugins could be managed outside of the Lambda environment (say, with wp-cli), with deployments automated… Then, it may be a little more applicable to a larger audience.

If you’re looking for a cheap solution to host your personal blog (like me!), you might just want to bite the bullet and check out any of the hosted WordPress solutions out there.

If you liked this post, or you’d like to provide some input, please do so in the comments. My favorite AWS service is Lambda, and I like pushing it a bit, so look forward to similar posts in the future. If you find bugs in the boilerplate, or you can make improvements, please open an issue or PR!

Miscellaneous Tidbits

  • Aurora Serverless sounds like it would be the best match for this setup. It probably is. Just keep in mind that Aurora Serverless doesn’t support publicly accessible clusters. To use it, you’ll need to go the Lambda-in-VPC, NAT gateway route.
  • Regarding public / private access and NAT gateways, if you’re like me and believe in the future of IPv6 and think that you can just use an egress-only internet gateway – you’re wrong! Lambda doesn’t seem to support IPv6 at this time.
  • You can actually use a NAT instance if the NAT gateway is overkill. However, I would recommend using the NAT gateway if you can. It comes with automatic scalability and redundancy, so you don’t have to babysit your NAT instance. (If you need more than one NAT instance, use the gateway. Seriously.)
  • At time of writing, my patches to php-lambda-layer haven’t been merged yet, so you can use my patched version (the boilerplate repository has this applied already).
  • If you’re really going all-in, consider using an Application Load Balancer rather than API Gateway to save money. API Gateway has zero fixed costs, but there is a point where ALB will become cheaper than API Gateway.
  • Doing some crude calculations, you should be able to handle an average of a few hundred users per day under the perpetual free tier. Your highest bill may be data transfer to the user.

App.Net Comments Widget on WordPress

I noticed that most of the good discussion I was having about blog posts were on App.net. Turns out, many other people feel the same way, and there’s even an official App.net comments widget!

I’ve been testing a WordPress plugin to replace the default WordPress comments with ADN comments on this site for quite a while, so I decided to publish it in the WordPress Plugin Directory. Just search for “ADN Comments”, and it should be the first result.

ADN Comments

Installing this plugin will completely replace WordPress comments. You won’t even be able to see them from the admin dashboard — the data is still there, but just hidden. If you don’t like ADN Comments, just disable the plugin and all your old comments will reappear.

If you’d like to display your username as the default “reply-to” username, please set your App.net username in the “Contact Info” section of your profile.

WordPress Admin => Users => Your Profile => Contact Info => App.net Username

Source code is available under the GPLv2 (or later) license. Code contributions, bug reports, etc, are welcome at the GitHub repository.

WordPress Meetup Tokyo — WordPress Server Optimization

I recently gave a quick talk about how I use Nginx, HHVM, MariaDB with WordPress on this blog at the March WordPress Meetup in Tokyo. Here are the slides:

I’ve published a Vagrant template for the setup detailed in the slides.

SSL and SPDY

In my quest for faster response times and page load speed, I’ve been playing around with Google’s SPDY. I finally got around to getting a SSL certificate for this website and installing the latest version of the SPDY module for Nginx.

WordPress, alone, doesn’t really support SSL on all pages out of the box — here are some extra things you probably want to implement.

  1. Redirect all non-HTTPS traffic to the HTTPS server. For example, this is what I use:
    server {
      listen 80;
      server_name keita.blog;
      return 301 https://keita.blog$request_uri;
    }
    
  2. Use HTTP Strict Transport Security:
    server {
      listen 443 ssl spdy;
      ...
      add_header Strict-Transport-Security "max-age=31536000";
      ...
    }
    

    Using HSTS has the benefit of letting the user agent know that all requests should be using the HTTPS protocol for this domain. This is important because some WordPress plugins and/or themes will prefer to use HTTP, even though the connection is HTTPS. I had a problem with some AJAX functions and Jetpack’s Infinite Scroll.

  3. (Optional) Install a HTTPS plugin. Not required, but it might help some problems with non-HTTPS content domains, et cetera.

WordPress on Nginx, HHVM, and MariaDB

Update 2014/4/24: I’ve updated the template to work with the latest HHVM 3.0+, and also squished some bugs.

I’ve been talking quite a bit about WordPress on HHVM recently, and it’s gotten a bit of attention, so I decided to open-source my Vagrant setup for running WordPress on HHVM.

I originally made this Vagrant setup to test HHVM on my local machine before deploying updates to the theme, plugins, etc.

Have fun, and please ping me if you have any problems!

GitHub link

Details

HHVM and WordPress

Update 2014/4/17: This site now runs WordPress 3.9, which seems to be working fine with HHVM. Also, compatibility has improved, thanks to a patch in the WordPress core specifically for HHVM.

I recently posted about how I switched out PHP-FPM (PHP’s FastCGI pool) for HHVM. Today I’ll be talking more about the install process on the server, and using it to set up WordPress.

What is HHVM?

This is usually what happens in a (successful) startup1:

  1. Initial codebase is written in an interpreted language, trading agility and developer productivity for slower response speeds and increased CPU usage on the servers.
  2. The service sees new users coming in and the initial codebase becomes strained.
  3. The service scales up the number of servers, but hits a wall.
  4. The codebase is re-written, either wholly or partially, in to a more performant and efficient language.

Facebook did steps 1 to 3, but refused to do step 4. Compiled languages, while having strengths in performance and efficiency, are usually harder to program in than interpreted languages. So, Facebook made something called HipHop for PHP. HipHop for PHP, or HPHPc for short, compiles PHP into C++, then compiles that into a big binary file. This allows PHP developers to do what they do best — develop in PHP, without worrying (too much) about performance.

As Facebook grew even more, HPHPc began to show its limitations2.

Out of this, came HHVM. HHVM is short for HipHop Virtual Machine, and uses a Just-in-Time (JIT) compiler to run PHP code. The main benefit developers do not need compile the whole binary every time they want deploy new code. With HHVM, Facebook has also worked towards full PHP 5.53 support, support of create_function() and eval(), enabling the majority of PHP application frameworks to work with HHVM.

This includes…

WordPress

WordPress is written in PHP, and runs very well on HHVM. Previously, getting WordPress to work on HHVM required some patches to the core code4, but these issues have been resolved.

I’ve been running this blog on HHVM for about a week now, and response time is consistently 10x faster than before — 100x if using something like Batcache.

Installation

This server is running CentOS 6.3. While there are packages with HHVM pre-compiled, these packages are not officially supported by Facebook, and are usually compiled by individuals. Your mileage may vary, but package conflicts prevented me from installing any pre-compiled HHVM packages — so I had to compile it myself.

If you plan on running a server with HHVM from scratch, I highly recommend using Ubuntu, and the official precompiled packages for Ubuntu 13.10 / 12.04.

Bugs / Things to watch out for

Because HHVM is not PHP, you may occasionally run into unexpected behavior. Here are some problems I had while setting this site up:

  • No MySQLi support — this means tools like phpMyAdmin and database backup plugins that rely on MySQLi won’t work. Edit: HHVM 3.0 and above have MySQLi built in.
  • In the WordPress “General Settings” area, setting “Timezone” to something like “Asia/Tokyo” caused WordPress to crash. The “UTC +/- (hour)” settings seem to work fine.

That’s all, for now. When I run into more problems, I’ll make sure I post about them (with a workaround, if available).


  1. Twitter is (in)famous for its use of Ruby on Rails, which eventually had to be re-written in Scala, a language that runs on the JVM.

  2. Compile time, compiled binary size, lack of MARKDOWN_HASH0f953f8e8cc058300e5041e6f079ab63MARKDOWN_HASH and MARKDOWN_HASH346e614b0b5901e8cdf0ca89b1b3950cMARKDOWN_HASH support, to name a few.

  3. Wow HHVM is fast…too bad it doesn’t run my code

  4. Getting WordPress running on HHVM « HipHop Virtual Machine

wp_enqueue your scripts and styles.

The ease of making a WordPress theme is both a blessing and a curse. It allows people to get started with programming relatively easy — most hosting providers have PHP / MySQL, and the majority have a WordPress quick-install. However, this easiness comes with a dangerous pitfall — it’s easy to do the wrong thing. The classic example of this is query_posts (hint: never use it).

Today, I’ll talk about wp_enqueue_script and wp_enqueue_style. If you have ever made a plugin that requires additional stylesheets or scripts in the front-end, you know exactly what this is (and why it’s important).

The easy way out in this situation is to manually write scripts and styles in header.php like this:

...
<script src="<?php echo get_template_directory_uri(); ?>/hello.js"></script>
...
<?php wp_head(); ?>
...

Although this will work, you may start to notice funny things happening when you want to use jQuery.

...
<script src="<?php echo get_template_directory_uri(); ?>/hello.js"></script>
<script src="<?php echo get_template_directory_uri(); ?>/my_jquery.js"></script>
...
<?php wp_head(); ?>
...

The problem here is that WordPress actually uses jQuery internally for the admin bar. And, it doesn’t know that you’ve packaged your own version of jQuery — so it actually loads twice, when the admin bar is showing. This can cause problems that are time-consuming to fix, and just a waste of time.

WordPress has a mechanism to deal with this, wp_enqueue_script and wp_enqueue_style. These functions will determine the order of scripts and styles based on dependencies, and will only output the styles / scripts that are needed. This kills a few birds with one stone: loading everything in the correct order, making sure nothing is loaded twice, and not wasting bytes on scripts or styles that won’t be used by the current page.

Enough talk! Let’s get into the code.

First, you’ll need a functions.php, with the following code.

/**
 * Enqueue theme scripts and styles
 */
add_action( 'wp_enqueue_scripts', 'THEME_NAME_register_scripts' );
function THEME_NAME_register_scripts () {
  // wp_register_script( script identifier, absolute URI, array of dependencies, version )
  wp_register_script('home', get_template_directory_uri() . '/js/home.js', array('jquery'), filemtime(dirname(__FILE__) . '/js/home.js'));

  if (is_home()) {
    wp_enqueue_script('home');
  }
}

In this example, there is a JavaScript file called “home.js” in the “js” directory. This file requires jQuery, so it will be loaded after jQuery has finished loading. At the bottom of the function, the is_home() conditional is used to load the script only when the home page is showing.

Instead of using conditional tags in the wp_enqueue_scripts action, you can also register your scripts in the enqueue action, then enqueue them in the actual template file. Add the following to the template file, before get_header();:

add_action('wp_enqueue_scripts', function() { wp_enqueue_script('home'); }, 15);

This will load the ‘home’ script that we set up in the earlier snippet.

Have fun! I would be interested to hear feedback — any cool uses of wp_enqueue, et cetera.

Documentation

The syntax of wp_enqueue_style and wp_register_style is similar to the script counterparts, and they go in the same wp_enqueue_scripts action.

Documentation for wp_enqueue_style

Documentation for wp_register_script, including the scripts that WordPress has pre-packaged for you!

Documentation for wp_enqueue_script

Turbolinks and WordPress

Rails Turbolinks is pretty cool, right? I thought it would be pretty cool to use it on WordPress, too.

There are probably a lot of bugs, and it probably doesn’t work well with JavaScript-heavy sites. I’ve included the jQuery compatibility layer, but it’s still not perfect. Use at your own risk.

To install, just search the WordPress plugin repository for “turbolinks”

wp-turbolinks

Or, available for download here:

http://wordpress.org/plugins/wp-turbolinks/

Contributors welcome!!!

WordPress 3.7: Automatic Updates

WordPress 3.7 Update Screen

WordPress 3.7 was just released.

Although there are quite a few features in this release, I want to talk about what I feel is the most important feature: automatic updates.

It’s a fairly simple – when a new version of WordPress is released, your installation will be updated (almost) immediately. As of 3.7, these automatic updates are limited to minor maintenance releases to make sure they won’t break your theme or plugin.

As WordPress has become more and more popular, exploits against WordPress have grown not just in quantity but also in complexity. As the threats have increased, so have the defenses — automatic security updates is just another step in the right direction. The last thing I want is for your blog / site to be compromised.

So, I strongly recommend updating to 3.7 as soon as existing themes and plugins have been verified to work with it (you’re using a staging environment… right?).

Happy updating!

Vagrant, WordPress, and Theme Development

I’ve been playing around with Vagrant recently. It really is a great tool for setting up development environments quickly and cleanly – no more local MySQL databases with 100 separate databases!

There are a few ways to solve this problem that many WordPress developers have:

  1. Use WordPress Multisite mode.
  2. Regularly clean your databases up and delete old ones.
  3. Use a common WordPress install, switching themes.
  4. Use Vagrant.

I’m going to be talking about the last option, Vagrant, in this blog post. I’ll list out a few reasons why Vagrant was attractive to me in the first place:

  • Isolation – this was appealing not only to reduce my database clutter, but also to be sure that the development and production environments were as similar as possible (within reason – of course).
  • Portability – another killer feature of Vagrant. Check out the repository, vagrant up, and you’re ready to go.
  • Coolness – don’t you love the idea of having contained, automatically managed environments for your projects? No? Well, I do.

Here’s what I came up with: vagrant-wp-theme-template.

It’s a template based on Underscores (_s), a template theme for WordPress themes. I’ve made two modifications: convert the CSS to SCSS, and the JavaScript to CoffeeScript. Grunt, an excellent automation tool, is used to compile the sources into CSS and JavaScript.

I’ve made the template compatible with _s, so just follow the instructions for _s regarding naming your theme, then the directions for getting your development environment set up. If you want to use an existing theme, just drop it inside the theme folder (and don’t forget to update the name in the Vagrantfile!)

I’m always open to new ideas and pull requests – please don’t hesitate to contribute!

Finally, this wouldn’t have been possible without the help of @miya0001‘s vagrant-chef-centos-wordpress, which this template is built off of. Thanks!