AWS S3 static hosting with SSL and user friendly URLs

Niels Filmer
Robot Kittens
Published in
7 min readMar 12, 2018

Recently Tim Rijkse decided to write about his experiences with Nuxt.js. I happen to know that his first project with Nuxt.js is our very own website robotkittens.nl and after he was finished creating our website it fell on my shoulders to arrange the appropriate hosting for the site. Also, I very much enjoyed reading his article, so I decided to write a bit about the setup I’ve created to host our website and the problems I encountered with it.

Configuring the platform

As we use Amazon AWS for our hosting platforms and the website is generated into a flat HTML and Vue.js application, it seemed the ideal
hosting platform would be a static website on S3. There would be no maintenance of a Node.js server, no need for up-scaling or down-scaling on busy or quiet moments and no payment for unused computing power. Everything would be handled by Amazon’s static file servers and, ideally, cached by Cloudfront.

AWS: for every hosting service you can imagine, and tons more you haven’t thought of yet. All in a convenient, incomprehensible and ever-changing dashboard.

When I was searching for a good way to set up the platform for our static website, I found this excellent article right here on Medium. It describes step by step how to configure S3, Cloudfront and Route 53 to host your static website on a custom domain. This includes the following steps:

  1. Create an S3 bucket and upload your website
  2. Create a cloudfront distribution pointing to this S3 bucket
  3. Request a new SSL certificate
  4. Assign the certificate to your Cloudfront distribution

The article is currently a little bit outdated, since you can now just use a button to validate the domain for your SSL certificate if you have it configured in Route 53, but I assume most of you should have no trouble following the tutorial to have your basic setup ready. So go on and read Setup AWS S3 static website hosting using SSL (ACM).

The problem

This is what your source looks like on Nuxt.js. Don’t start Nuxting if you like looking at your source.

Robotkittens.nl is a portfolio site and we have a few projects we are proud of on there (take a look if you haven’t done so already, it has a 3D cat!). A deal breaker would be our inability to deeplink to our projects. Unfortunately the aforementioned tutorial is for a single page website. You can still deeplink, but you have to add /index.html after a folder, which is in my opinion inconvenient and ugly.

The problem lies within hosting a site on S3 through CloudFront. CloudFront uses the S3 API to find files and the API is a bucket with file references, imitating a folder structure. Enabling website hosting for a S3 bucket will allow you to redirect traffic to the S3 HTTP endpoint which simulates a webserver, but when using CloudFront in front of it you are not allowed to do that anymore. It effectively disables the Apache- or Nginx-like behavior where you can assign a directory index. CloudFront does not know you are referring to a folder.

Assuming there would be a simple configuration to enable this behavior, I lost an hour or so looking for a checkbox I assumed existed, but could not find. Apparently, when having a platform which can do everything, you will loose the ability to do something easily.

The solution

Couldn’t be bothered to find a solved one on Unsplash.

After clicking through the nightmarish interface AWS offers for a while, and after googling and finding articles which offer non-working solutions, I encountered an article on aws.amazon.com about this very problem: Implementing Default Directory Indexes in Amazon S3-backed Amazon CloudFront Origins Using Lambda@Edge.

Of course, it is a bad user experience to expect users to always type index.html at the end of every URL (or even know that it should be there). Until now, there has not been an easy way to provide these simpler URLs (equivalent to the DirectoryIndex Directive in an Apache Web Server configuration) to users through CloudFront. Not if you still want to be able to restrict access to the S3 origin using an OAI. However, with the release of Lambda@Edge, you can use a JavaScript function running on the CloudFront edge nodes to look for these patterns and request the appropriate object key from the S3 origin.

When I read that I became simultaneously ecstatic and concerned. It describes exactly the problem I’ve been having and offered a solution directly from someone at AWS, so it had to work. Using a Lambda function to imitate Apache-like behavior however, sounded like an incredibly complicated solution for a simple problem.

AWS Lambda

For those that don’t know yet, AWS Lambda is a service that lets you run pieces of code on demand, without the need to manually launch a server. In their own words:

AWS Lambda lets you run code without provisioning or managing servers. You pay only for the compute time you consume.

I’ve experimented with Lambda before and while I find the platform incredibly promising, I just sometimes use it to resize images on S3, which is kinda like using humanoid robot to pass you the butter. Demanding a bit of server time on a request to CloudFront seemed like it would have a big impact on performance though.

Please wait while Lambda boots a few scripts before we can show you the page you want.

The scripts

The article goes into detail how to configure Lambda and CloudFront to trigger and modify the request that’s routed to your files on S3. It is apparently a new type of Lambda, called Edge, which is only available in the region us-east-1 (N. Virginia), which isn’t specified in the article, so hopefully I have at least prevented the second time searching through the AWS console I had to do.

Basically, you add the script provided in the article and create triggers for each CloudFront distribution you want to have a static website hosting. The script will hijack the request and add index.html after it. A request to /projects/ will become a request to /projects/index.html in the Amazon architecture. The user won’t notice.

I didn’t want to have to add a slash after each URL to act as a folder, so I slightly modified the script to add either /index.html or index.html after each request, provided there is no . (dot) in the path (otherwise images and stuff would be affected).

'use strict';
exports.handler = (event, context, callback) => {

// Extract the request from the CloudFront event that is sent to Lambda@Edge
var request = event.Records[0].cf.request;
// Extract the URI from the request
var olduri = request.uri;
var newuri;
if(olduri.indexOf('.') > -1) {
newuri = olduri;
} else {
// Match any '/' that occurs at the end of a URI. Replace it with a default index
newuri = (olduri.substr(-1) == '/') ? olduri + 'index.html' : olduri + '/index.html';
}
// Log the URI as received by CloudFront and the new URI to be used to fetch from origin
console.log("Old URI: " + olduri);
console.log("New URI: " + newuri);

// Replace the received URI with the URI that includes the index page
request.uri = newuri;

// Return to CloudFront
return callback(null, request);
};

The speed

Any doubt the Lambda requests would have an impact on performance disappeared. Apparently CloudFront also caches the Lambda results, so repeated requests to the same path would not trigger the Lambda scripts to run, but just return the required content. Also I didn’t notice any performance loss when they do needed to run.

When tested, the response time of the servers was somewhere between 2ms(!) and 60ms in the US and Europe. In Asia the response is considerably slower, but that is because I configured CloudFront for use in US and Europe only to have the cheapest possible configuration. With some extra configuration of caching headers to the right folders, our website is at grade A hosting.

The average load time of the complete site is now generally somewhere below 1 second

The future

Currently, deployment is an issue. To maintain the portfolio I still have a database and server running to have a CMS and API somewhere. When running nuxt generate, the building process will read the json from the API and build the website, which means every time someone alters our portfolio, nuxt generate has to be ran.

I’m considering building a general deployment server to do the building for all our static websites using Adonis.js. Ideally deployment would run through Lambda, without any real servers. This is how the website of Nuxt.js is currently set up. I don’t like git based CMSs though and prefer to go the API driven way, and I haven’t found a way to host a complete API driven CMS on Lambda alone. I found some options for SAAS CMS & API (https://headlesscms.org/ has a good overview), but those are costly and we currently have a similar, although decentralized, solution in place.

Do you have any alternative ways to deploy your static website to a hosting? I would love to hear in the comments.

--

--

Niels Filmer
Robot Kittens

I like Amazon AWS, back-end development in any language and hot sauces. Co-Founder of Robot Kittens https://robotkittens.nl