Pairing WordPress with Amazon’s CloudFront CDN

Like everybody else on the planet, I’m very fond of stuff that just works. Many web developers, myself included, operate on the assumption that something will always break, and that makes it all the sweeter when it doesn’t. With the 2014 version of our company website, we decided to implement Amazon’s CloudFront CDN for all our static content.

And, what can I say, it just works. Here’s how to rock CloudFront with WordPress.

Getting started with AWS

You’ll need an account on Amazon, the same kind you use to buy stuff from them. Then, go to the AWS (Amazon Web Services) Management Console. Sign in and then veer to the far left; there’s CloudFront, subtitled ‘Global Content Delivery Network’.

Create a distribution

In this case, a distribution refers to content (such as stylesheets and images) fetched from your web server by CloudFront and then distributed across what are known as Edge Locations. These are servers strategically placed all over the world, allowing users to download static content from a source that’s much closer to where they are, significantly reducing transfer times. This means CloudFront will store several copies of your files, all spread out on different servers.

Click the blue button in the top left, the one marked ‘Create Distribution’. Then choose ‘Web’ as the delivery method. Now, you should be looking at a lot of options, most of which we can safely ignore.

Origin domain name

This is the domain CloudFront will use to get the copies of your content. It can be anything other than your primary domain. I recommend you use static.yourdomain.com, or even better, the same pattern but on a different domain. (For instance, Avidmode serves static content via static.avidmode.io rather than .com.)

Setting up a domain for your static content requires configuring WordPress a bit, and we’ll get to that later.

Origin ID

If you have multiple domains serving content for the same site, this is the option you use to separate them. You won’t need this to start with, but you should still give it a sensible name, such as cloudfront-static.yourdomain.com.

Leave every option at its default value and keep scrolling until you get down to the ‘Distribution Settings’. We’re going to specify just one more option.

Alternate Domain Names (CNAMEs)

A CNAME is an alias. It points to a different domain rather than an IP address. We’re going to use this in order to achieve our nice static.yourdomain.com. Write your desired static domain in the box and then we’re done on this page. Scroll down and hit the blue button at the bottom, ‘Create Distribution’.

Setting up our static domain

Now, you’ll see your newly-created distribution being deployed. The third column, marked ‘Domain Name’, contains a strange-looking *.cloudfront.net domain that we need for our CNAME. You could use it as-is without the CNAME, but it wouldn’t be as pretty.

Now, fire up your DNS manager and navigate to the domain you want to use, in all likelihood yourdomain.com, and find where it says CNAME. There’ll be a button nearby for creating a CNAME record.

In the hostname field, type in cdn-static.yourdomain.com. In the alias field, paste the *.cloudfront.net domain you copied off your CloudFront distribution. You can ignore any other fields and just save the record.

Next, we need to create an A record. This time, type in only static as the hostname. For the IP address, use the same as your default A record. It will be one with either no hostname or www.

Server-side

This static.yourdomain.com is the one from which your web server will deliver content to CloudFront. To configure the server, we create a separate configuration for the static domain, one that only allows static content and disallows script files. In nginx, it looks something like this.

server {

  listen                your.server.IP.address;
  server_name           static.yourdomain.com;
  root                  /path/to/yoursite/public_html;

  location ~ \.(php|pl|cgi|scgi|exe|asp|py|aspx)$ {
    deny                all;
  }

  location ~ \.(js|css|png|jpe?g|gif|ico|svg)$ {
    expires             max;
  }

}

How CloudFront distributes your content

Whilst your distribution and CNAME are cooking (the AWS CloudFront console will tell you when the distribution has been deployed and you can use this tool to check if your CNAME has propagated globally), it’d be good to get our heads around the way CloudFront does its thing.

The CDN delivery chain

A person comes to your website. In order to see it, their browser needs to download a whole bunch of stuff. As an example, one of these may be your logo, which has the URL http://cdn-static.yourdomain.com/images/logo.png. (We’ll get to making WordPress use a static domain further down.) Now, because cdn-static.yourdomain.com is our CNAME, the request will be sent to CloudFront.

Based on the user’s location, they will be given the IP address to the CloudFront edge location which is closest to them. This server will then receive the request for logo.png and check if it has a cached copy of it. If it doesn’t, it will send a request to your server via static.yourdomain.com, which we set as our origin.

Your server will dutifully send over logo.png and the CloudFront edge location will save a copy before passing it along to the visitor. The next time someone in the same geographic region requests the same file, CloudFront will serve its cached copy. This process will take place for all the edge locations which will need their own copies of your files.

A simplified illustration of the CloudFront delivery chain

 

The best part of this is that it just works. You set up the static domains on your end, add CloudFront as a layer in front (hence the name; CDNs are cloud-based tech) and it takes care of the rest.

Using a static domain with WordPress

By default, your WordPress content URLs look like /wp-content/themes/yourtheme/, possibly with /wordpress in front if you’ve got it installed in a subdirectory. This works for most circumstances but it’s not the ideal. We need WordPress to serve everything but the permalinks from a different domain. This’ll take equal parts configuration and jerry-rigging.

Theme and plugin files

Amongst the many great constants we have at our disposal is WP_CONTENT_URL. It points to and is what WordPress will use to serve everything inside the wp-content directory, which holds themes, plugins and uploads. This means we can use it for our static domain.

Open up your wp-config.php file and add the following two lines, anywhere except at the bottom. (Make sure the constant hasn’t already been defined above or this won’t work.)

define( 'WP_CONTENT_URL', '//cdn-static.yourdomain.com/wp-content' );

Obviously, you’ll want to replace yourdomain.com with your actual domain, and add /wordpress before /wp-content if you’ve got WordPress in a dubdirectory.

Uploads

This one is trickier. There is a constant for changing the uploads directory but it won’t do what we need it to. Even if it did, it wouldn’t affect the images in your existing posts. We’re going to use a few filters here.

New attachments

The code below will take care of any future attachments. It will also handle upload URLs which aren’t hard-coded, such as post thumbnails. Add it anywhere in your theme’s functions.php.

add_filter( 'wp_get_attachment_url', function( $url ) {
  return str_replace( get_option('siteurl') . '/wp-content', WP_CONTENT_URL . '/uploads', $url );
});

Old attachments

Once an image has been added into a post or page, its URL is no longer passed through the wp_get_attachment_url filter we added above. In order to change it, we have to run a regular expression on the the_content filter. It’s not as clean but it’s a lot more convenient than manually editing every image. Add the below code to your theme’s functions.php.

add_filter( 'the_content', function( $content ) {
  return preg_replace( '/src=("|\')?[^"\'\s]+uploads\/([^"\'\s]+)["\'\s]/', 'src="' . WP_CONTENT_URL . '/uploads/\2', $content );
});

This regex checks for src attributes pointing to an uploads directory and replaces that part of the URL with the constant we defined earlier. It supports double and single quotes, as well as unquoted attributes (à la HTML5).

And that’s it. Your static content is now served via CloudFront.

Final considerations

This article assumes an untouched, default WordPress install. If you’ve made some modifications to yours, this stuff is likely simple enough to adapt. That brings us the last, perhaps most important question.

Do I need to use a CDN?

Outside of big enterprise websites or smaller ones which have a very large audience, a CDN is almost invariably icing on the cake. Strictly speaking, no, you don’t need a CDN, but using one will make your website faster, possibly significantly so, and everybody loves a fast website.

If you’re running an e-commerce website with lots of product images, the benefits of using a CDN increase greatly. A site that takes less than two seconds to load has potentially more than twice the conversion rate of a slower site. Also, watching images load is such a downer; a subtle reminder for those of us who remember the dial-up modem.

Using a CDN with WordPress

When you’re using a CDN, you can’t change static content (such as images) to new versions and see an instant update. Even a hard refresh won’t do it, because the CloudFront edge location will continue to serve its own copy until it decides to check back for a new version. This behaviour can be configured but that’s beyond the scope and complexity of this article.

You can get around this by uploading new versions of existing images with a different filename. CloudFront will see the new URL and start afresh. Consider using dummy filenames until you’re sure it’s the final version, at which point you can upload it using the proper, SEO-certified filename.

In closing

It’s much easier to shave three seconds off a site that loads in five than it is to bring two seconds down to one. Run your site through WebPagetest. If the Start Render time is less than or around two seconds, making it noticeably faster is a significant undertaking that will likely involve server administration and finely tuning every aspect of the website.

As always, weigh every investment against its possible return.

Until next time.

Comments