December 30, 2019
A Super-Charged Static Website Setup
For static websites, like this one, an AWS best practice is to host the static content in an S3 bucket and serve it to your users via the AWS CDN, CloudFront.
There are lots of examples of how to set this up. There are also lots of Terraform modules that solve this problem. In this post I will outline our Terraform module solution to get a secure static website backed by S3 / CloudFront set up easily:
So what does it do? Lots!
This one is kind-of a given, since one of the main reasons people choose S3 / CloudFront to deliver content is that they don’t have to manage any instances.
CloudFront has a feature which can redirect users who access your site via
http to the
https equivalent - we set that option on the CloudFront distribution. This module doesn’t provision an ACM certificate for you - you have to provide a certificate ARN and it will attach it to the CloudFront distribution.
Encrypts data at rest and in transit
Since we are using CloudFront, our content is cached at edge locations all over the world. One option to note is the PriceClass option, which we have set to PriceClass_All (the default), meaning that objects are served from the edge location for which latency is lowest for that viewer. Future versions may allow the customization of this parameter to for cost reduction purposes.
Supports restricting access to website by IP
The module supports specifying one or more IP ranges. If you do, then it will provision a WAF which will filter traffic to allow only those IP ranges. We use this feature for our
dev sites to limit access to only Ordinary Experts staff.
Sets up nice DNS entries
This module sets up two Route 53 records, one for the provided domain and one for the domain prefixed with
www.. As part of the set up for this module, you need to create a hosted zone that matches the domain that you would like the website to use. The module creates these records under that hosted zone.
Deploys new changes when commits are pushed to a CodeCommit branch
This module pulls changes from a CodeCommit git repository via a CodePipeline pipeline. You specify a git branch (default
master) that the pipeline will poll for changes. Since we use git-flow, we use this feature to automatically deploy changes to our dev site when we push to our
develop branch and to production when we push to our
Clears the CloudFront cache after deployments
Since CloudFront’s whole goal is to cache content close to the viewer, when new changes are pushed to your origin bucket, some viewers may not see those changes until the CloudFront cache of that object expires for that location. To address this, CloudFront has a method of invalidating files, which will cause them to be re-fetched from the origin. We trigger an invalidation of all the CloudFront files after each deployment.
Sends notifications on successful and failed deploys
After pushing a commit, it takes a few minutes for the change to flow through the pipeline. Once it is done, the email specified in the parameters will get a somewhat generic notification saying that the deployment succeeded or failed.
For a static site of any size, it is nice to use a site generator to help with the common elements, i.e. header / footers, etc. This module supports a build step where the site can be generated from its source. By default, the build is done by running a script called
build in the top-level of the repo. For example, this website uses Hugo as its generator - our
build file looks like:
#!/usr/bin/env bash wget -O /tmp/hugo.deb https://github.com/gohugoio/hugo/releases/download/v0.31.1/hugo_0.31.1_Linux-64bit.deb dpkg -i /tmp/hugo.deb hugo
This installs Hugo on the CodeBuild instance and runs
hugo to generate the site into the
public folder before it is uploaded to S3.
Supports URLs of subdirectories that do not have trailing slashes or
When using CloudFront to serve websites from a private S3 bucket, then CloudFront won’t automatically serve the
index.html files from subdirectories of your website. This module uses a Lambda@Edge function that inspects incoming requests and, if they don’t have an extension, adds a trailing slash and
Supports custom error pages
CloudFront allows the specification of custom error page paths for different error codes. Currently this module allows you to specify a custom error page path for
404 errors, so the custom page will be seen for unknown pages as well as when unauthorized users try to access a site with whitelisting turned on. Here is an example of a custom error page from our IP whitelisted dev site dev.ordinaryexperts.com.
Supports debug mode which turns off CloudFront caching
Finally, when working on this module there were times when it was nice to always have CloudFront fetch content from the origin. This can be done by setting the debug variable to
true, which causes the
MinTTL CloudFront values to all be set to 0.
We have an example Terraform configuration on the GitHub README, however there is some pre-work that needs to be done before you can do
One thing before you start - currently this module can only be deployed into the
us-east-1 (N. Virginia) region - so make sure you select that region when creating region-specific resources.
First, you need to set up a Route 53 Hosted Zone for the domain of the website you want to host (not including the
www). Once that Hosted Zone has been created and delegated, you are ready to create the SSL certificate.
When creating the SSL in ACM, be sure to specify the domain of the website you are hosting (again without the
www) as well as the domain prefixed with
*., i.e. if
marketing.yourdomain.com was the domain, then also add
*.marketing.yourdomain.com to the SSL certificate request. Since we have the Hosted Zone already setup in Route 53, if you chose the DNS validation option then the console will give you the option to automatically create the needed DNS records right from the SSL certificate wizard - easy!
After you have the Hosted Zone and the ARN for the SSL certificate you just created, you are almost ready! The last step is to create a CodeCommit repo that will host your website content. After you create the repo you can start by creating a sample
index.html file in the
public directory of the repository. FYI check out our post on authenticating to CodeCommit with AWS Vault.
Now you have all the values needed - from this point check out the README.md on GitHub for deployment examples.
Good luck & happy coding!