Using Custom Fonts with AWS OpsWorks, Cloudfront and Rails 4.1

We attempted to update the fonts of course apps to custom fonts and it was a very painful experience. Here is the summary of the challenge and the various solutions we banged our heads against, and finally a solution that worked.

The setup

The following problem and solution will only apply to you if you are using a setup similar to ours. We have an AWS Opsworks hosted Rails app which is using Cloudfront as the CDN then you will face some interesting challenges.

The Problem

The browser is throwing this error and refusing to load the font because the font is coming from a different domain (e.g. dxxxx.cloudfront.net) vs. what is displayed in the address bar. When browser did a GET request on the font file from cloudfront, it did not get the Origin header allowing the site in the address bar to use this font... In a way, it is preventing you from hot-linking a font file. Even though you it is a licensed font and you have it in your repo assets.

Why does this problem exist?

Fonts are special assets, unlike images, js and css, they come with higher intellectual property protection standards and browsers are starting to recognize that. If your assets are being loaded via cloudfront's SSL URL then everything except fonts will load fine, browser console will show CORS error.

Rule out "quick fixes" for opsworks / cloudfront setup

You can whitelist CORS settings on cloudfront; while necessary, this is insufficient, because cloudfront will just cache the headers as provided by your load balancer (meaning your nginx/app instance)

Changing nginx configuration would have been an easy fix but this will not work because if you are using Opsworks standard setup/deploy recipes, nginx header customization is not an option for you

font-asset gem will not work because you rails app will not get to serve the font assets at all, assets will be served by nginx by default as it sees the assets in public/assets directory and saves your rails stack from having to serve these assets

Setting "config.serve_static_assets=true" will not work, again, nginx is setup in standard opsworks setup to serve as a reverse proxy and will serve a precompiled asset (font) just as it would serve an image without hitting your rails app

rails-cors gem will not work, again for the same reasons as before, pre-compiled assets get served by nginx, there is no easy way to reach back into your app where gems like rails-cors and font-asset could help

The Hack

We were able to get custom fonts to work only by setting up another origin in cloudfront which would hit an s3 bucket for all paths with "webfont.*" in their names. For this to work the font had to renamed to have "xxx-webfont.ttf" filenames. Here is the list of changes/setup we created to fix the CORS pain:

For your cloudfront distribution, which already has a Default origin setup, add a new origin which will point to this s3 bucket

S3 bucket permissions

You can have your s3 setup as a "public website" if you are ok with all the fonts in your bucket to be publicly accessible, we didn't. We created the bucket with default permissions and then let cloudfront Origin setting create a special policy to access this s3 bucket

Now create a new Origin in Cloudflare; in Origin Settings make sure to:

Set Restrict Bucket Access = Yes

In Your Identities, let it Create a new Identity (this will be used by cloudfront to access your restricted s3 bucket which has the fonts)

Set Grant Read Permissions on Bucket = "Yes, Update Bucket Policy". This will create the policy for the bucket and let cloudfront access it

In Origin Behavior make sure to set the path pattern to be "-webfont.*" which will make sure that any path having a webfont request will be directed to the s3 bucket origin which you just created; all other paths will be hitting your standard rails stack and will be cached as before

Also make sure to set Forward Headers as whitelist, which will allow you to add Origin as a whitelisted header

Now in your Rails app, your CSS needs to have "url" instead of rails font URL helpers. The reason for this is simple. These font files are static, we do not expect to be generating digests for them and having "url" will allow us to access these files by their origin names instead of one which has the has digest appended to it. We wanted to keep things simple as we will not be changing the fonts too frequently, and if we do, the filenames will be different enough thereby not needing of the hash digest (vs. assets like application.js, which change frequently while having the same filename). Ultimately, you can always use query string to override the caching behavior if you are really concerned that stale font files are hanging around in the interwebs for too many hours infecting people's vision. If you really want to have the precompiled hash-digest files then you will need to copy these to the s3 buckets, you have three options here, we didn't bother with this, we are ok with copying over the font files manually to the bucket as part of our process:

In your chef recipes, you can have a step after asset pre-compilation to copy the assets to the bucket

If you use a continuous integration setup (we use Codeship), then you can use that service to precompile and copy assets (font files, e.g. *.ttf, *.eot, *.wof*) over to the s3 bucket with every deploy

You can use asset-sync gem to copy over the assets after pre-compliation set

With these changes in place, we were able to get the fonts to work in Chrome, Firefox and Hindernet Exploder. Too many hours wasted to learn all this. Wish AWS had made it simple to customize the nginx settings...