Code bites

2 notes &

Amazon Cloudfront, Nginx and the Rails Asset Pipeline

Amazon Cloudfront lets you distribute your assets(images, css, javascript) to locations around the world, giving faster load times for your users and also reducing the load on your servers. The assets generated by the Asset Pipeline introduced in Rails 3.1 are perfect matches for Cloudfront, and using it should be just a matter of pointing it to your server. But in setting it up myself I discovered that there some things to watch:

  • Gzip compression should be turned on for HTTP 1.0
  • If the Last-Modified header is present files may not be updated correctly at the expire time

I have therefore documented my setup here in case someone else could find it useful.

Prerequisites

To use Amazon Cloudfront the way described here your Rails app must be at version 3.1(or higher) and must be using the Rails Asset Pipeline. You also need an account with Amazon Web Services.

Configuring your Nginx webserver

Regardless of using Cloudfront or not I prefer to serve assets from a separate subdomain like for instance assets.example.com. This just feels cleaner to me and makes it easy to ensure that only assets are served through Cloudfront. Here is what the Nginx configuration looks like:

server {
  listen 1.2.3.4:80;
  server_name assets.example.com;

  root /home/user/apps/example/current/public;

  error_log /home/user/logs/error-assets.log notice;
  access_log /home/user/logs/access-assets.log main;

  location / {
    deny all;
  }

  location ^~ /assets/ {
    allow all;
    gzip_http_version 1.0;
    gzip_static  on;
    expires      365d;
    add_header   Last-Modified "";
    add_header   Cache-Control public;
  }

}

The configuration explained:

  • allow all Allow access to everything inside the assets directory, everything else is blocked by the deny all statement above.
  • gzip_http_version 1.0 If you do not include this setting Cloudfront will NOT serve gzipped versions of the files. As a result the files you are serving will be significantly larger, potentially nullifying the speed you gain from using Cloudfront!
  • gzip_static on When Rails precompiles the assets it also generates a gzipped version of css and javascript files. This setting is telling the server to check for these file and serve them in stead of gzipping the file every time someone asks for it. Note that to use this you must include the HttpGzipStaticModule when compiling Nginx. You do this with the —with-http_gzip_static_module flag.
  • expires 365d Indicates that the assets should be cached for one year. So browsers that downloads the assets should not ask for them again before one year has passed. It is my understanding that Cloudfront also uses this to decide how long to store the assets. Normally the assets files are small so the cost of storing them for one year should be minimal, but should you have very large assets that are changed frequently you may want to consider a lower value here.
  • add_header Last-Modified “” This removes the Last-Modified header. We have experienced that when the Last-Modified header is present the file may not be updated correctly when the expires time is reached. This caused the browser to make if-modified requests for the asset every time the page was loaded. Not sure why this is happening, but apparently this header should be removed anyway as, according to among others the Asset Pipeline Rails Guide, it’s presence may cause some browsers to request the file again before the expires time is reached.
  • add_header Cache-Control public This sets Cache-Control to public. This indicates that the assets can be stored in public caches, like for instance in a proxy.

Note that to add the settings related to gzip you have to enable gzip for Nginx in the first place. You do this in the nginx.conf file, and here are the settings that are working for me:

gzip  on;
gzip_http_version 1.1;
gzip_vary on;
gzip_comp_level 6;
gzip_proxied any;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
gzip_buffers 16 8k;
gzip_disable “MSIE [1-6].(?!.*SV1)”;

This configuration should be good also for apps where Cloudfront is not used. The only setting specific to Cloudfront is gzip_http_version 1.0 so this can be removed.

Creating the Cloudfront distribution

Next step is to create the Cloudfront distribution, we are going to go with the defaults of no HTTPS and no logging. Sign in to the AWS Management Console, then click “Create distribution”:

Select “Custom origin”, enter the subdomain configured above and click Continue:

On the next screen enter the domain that should be used to access the assets, then Click continue:

Your distribution has now been created, note that it may take a few minutes before it is ready.

Configuring DNS

The distribution you created has a domain name that looks like xxxxxxxxxxxxxx.cloudfront.net. To make it look a little nicer you can create a CNAME record that points to this for the domain entered when creating the distribution, in our example cloudfront.example.com.

cloudfront.example.com -> xxxxxxxxxxxxxx.cloudfront.net

Note that if the distribution is accessed through HTTPS you should not use a CNAME as this will create security warnings in the browser.

Using it in Rails

To start using the Cloufront distribution add it to production.rb as follows:

config.action_controller.asset_host = "http://cloudfront.example.com"

After you deploy this all requests for assets will be sent to Cloudfront, and Cloudfront directs the request to the location nearest the user. If a copy exists on this location Cloudfront will return it, if not it will forward the request to your server and store the asset returned.

  1. codebites posted this