Fetching contributors…
Cannot retrieve contributors at this time
209 lines (153 sloc) 5.23 KB

DNS to CDN to Origin

Content Distribution Networks (CDNs) such as Amazon CloudFront and Fastly pull content from their origin server during HTTP requests to cache them:

DNS -> CDN -> Origin


DNSimple -> Fastly -> Heroku
DNSimple -> Cloudfront -> Heroku
Route 53 -> CloudFront -> S3
Route 53 -> CloudFront -> EC2
Route 53 -> CloudFront -> ELB -> EC2

Without an asset host

If a CNAME record for a domain name points to a Rails app on Heroku: ->

Each HTTP request for a static asset:

  • is received by the Heroku routing mesh (platform load balancer)
  • picked up by a web dyno (host)
  • passed to one of the Puma workers (web server process) on the dyno
  • routed by Rails to the asset (CSS, JS, img, font file)

The logs will contain lines like this:

GET "/assets/application-ql4h2308y.js"
GET "/assets/application-ql4h2308y.css"

This isn't the best use of Ruby processes; they should be reserved for handling application logic. Response time is degraded by waiting for processes to finish their work.

With a CDN as an asset host

In production, Rails' asset pipeline appends a hash of each asset's contents to the asset's name. When the file changes, the browser requests the latest version.

The first time a user requests an asset, it will look like this:


A CloudFront cache miss "pulls from the origin" by making another GET request:


Future GET and HEAD requests to the CloudFront URL within the cache duration will be cached, with no second HTTP request to the origin:


All HTTP requests using verbs other than GET and HEAD proxy through to the origin, which follows the Write-Through Mandatory portion of the HTTP specification.

Rails configuration

In Gemfile:

gem "sass-rails"
gem "uglifier"

In config/environments/production.rb:

config.action_controller.asset_host = ENV.fetch(
config.action_mailer.asset_host = config.action_controller.asset_host
config.assets.compile = false
config.assets.digest = true
config.assets.js_compressor = :uglifier
config.public_file_server.enabled = true
config.public_file_server.headers = {
  'Cache-Control' => "public, max-age=#{10.years.to_i}, immutable",

The immutable directive eliminates revalidation requests.

CloudFront setup

To use CloudFront:

  • "Download" CloudFront distribution
  • "Origin Domain Name" as
  • "Origin Protocol Policy" to "Match Viewer"
  • "Object Caching" to "Use Origin Cache Headers"
  • "Forward Query Strings" to "No (Improves Caching)"
  • "Distribution State" to "Enabled"

Fastly setup

To use Fastly, there's no additional "Origin Pull" configuration.

This is a handy task for the app's Rakefile:

task :purge do
  api_key = ENV["FASTLY_KEY"]
  site_key = ENV["FASTLY_SITE_KEY"]
  `curl -X POST -H 'Fastly-Key: #{api_key}'{site_key}/purge_all`
  puts 'Cache purged'

Then, the deployment process can be adjusted to:

git push heroku master --app example
heroku run rake purge --app example

For more advanced caching and cache invalidation at an object level, see the fastly-rails gem.

Caching entire HTML pages

Setting the asset host is the most important low-hanging fruit. In some cases, it can also make sense to use a DNS to CDN to Origin architecture to cache entire HTML pages.

Here's an example at the Rails controller level:

class PagesController < ApplicationController
  before_filter :set_cache_headers


  def set_cache_headers
    response.headers["Surrogate-Control"] = "max-age=#{10.years.to_i}"

To cache entire HTML pages in the CDN, use the Surrogate-Control response header.

The CDN will cache the page for the duration specified, protecting the origin from unnecessary requests and serving the HTML from the CDN's edge servers.

To cache entire HTML pages site-wide, one approach is Rack middleware:

module Rack
  class SurrogateControl
    def initialize(app)
      @app = app

    def call(env)
      status, headers, body =
      headers["Cache-Control"] = "public, max-age=#{5.minutes.to_i}"
      headers["Surrogate-Control"] = "max-age=#{10.years.to_i}"
      [status, headers, body]