Setting up an HTTPS static site using AWS S3 and Cloudfront (and also Jekyll and s3_website)
For a while now I wanted to migrate my websites away from Github pages. While Github provides an excellent free service, there are some limitations to its capabilities, and the longer I wait the harder (or the more inconvenient) it becomes to migrate away from gh-pages. AWS S3 + CloudFront is a widely-used alternative that has been around for a long time. Moreover, I was planning to get more familiar with AWS at all levels anyway. So, it’s a great learning opportunity too.
There are a number of very helpful tutorials online on how to set up an HTTPS static site using AWS S3 and CloudFront. Of course, as always the case with blog articles, they may be outdated, incomplete, and generally not as trustworthy as the official AWS documentation on the topic, which is pretty good too; but it is also somewhat fragmented and inconvenient to follow. So I wrote my own summary to refer to in the future.
1 Set up a static site, yet without CloudFront and without HTTPS
First, we set up a static HTTP site without a custom domain on AWS S3:
- Create a bucket named
example.comwith your own domain).
- Follow the procedure given at https://docs.aws.amazon.com/AmazonS3/latest/dev/HostingWebsiteOnS3Setup.html to enable website hosting for the bucket, and to make it publicly readable; (optionally) if you want to understand the AWS bucket access policy language see https://docs.aws.amazon.com/AmazonS3/latest/dev//access-policy-language-overview.html, and follow the links from there.
- Test the S3 website: Upload an
index.htmlto the bucket (you can keep all options for the upload at their default values). Then go to
http://example.com.s3-website-us-east-1.amazonaws.com/(where you need to replace
example.comwith the bucket name, and
us-east-1with your bucket’s region), and see if the contents of
Yay we have a working website!! …without a custom domain or https yet
The www subdomain: Now prepare another S3 bucket for the subdomain “www.example.com” to be later redirected to the root domain “example.com” (btw, if you so wish,
www.example.com can be the main S3 bucket and the
example.com bucket can be configured to redirect — just swap their roles in this entire writeup):
- Create a bucket named
www.example.com(all options can be left at their defaults; this bucket doesn’t need to be publicly readable).
www.example.comto redirect all requests to
example.comfollowing Step 2.3 from the AWS docs at https://docs.aws.amazon.com/AmazonS3/latest/dev/website-hosting-custom-domain-walkthrough.html.
- Test the endpoints for the redirect by going to
http://www.example.com.s3-website-us-east-1.amazonaws.com/(as before replace the bucket name and region accordingly).
Map the domain and subdomain to their S3 buckets:
Amazon Route 53 is a service that maintains a mapping between the alias records and the IP of the bucket. You need to follow Step 3 from the AWS docs at https://docs.aws.amazon.com/AmazonS3/latest/dev/website-hosting-custom-domain-walkthrough.html.
Configuration with your domain name registrar:
- In AWS go to Route 53 -> Hosted zones -> example.com
- The NS (name servers) records that you see are what needs to be provided to the domain name registrar. For example, for GoDaddy I have to choose to use “custom nameservers” under the DNS settings for the domain, and then to input all (four in my case) of the URLs provided as values under the NS record.
- Your website should now appear under http://example.com (and http://www.example.com).
So we have a website with a custom domain!! …though without CloudFront (so loading may be rather slow) and without HTTPS.
Optional: Configure an IAM role with limited access permissions
Now it seems a good idea to create a new user that has full read-write permission to the
example.com bucket and full permission to CloudFront, but does not have any further AWS permissions.
A suitable IAM policy document can be found at: https://github.com/laurilehmijoki/s3_website/blob/master/additional-docs/setting-up-aws-credentials.md
Make sure to save the new user’s access key ID and secret access key somewhere in a private place.
Optional: Use Jekyll and s3_website to generate a static site and to push it to the S3 bucket
Well, I typically use Jekyll to make my static sites (because it’s awesome!).
The Ruby gem
s3_website can be used to push the website to, or to synchronized it with the S3 bucket.
s3_website documentation is easy to follow.
I have found it convenient to use the
dotenv gem to keep the access key ID and the secret access key of the user (that was just created) locally in a
.env file (don’t commit/push it to github!!!)
At this point you may also choose to allow
s3_website to set up CloudFront for the website to save some time later (though without the SSL certificate, which will still have to be added manually, see below).
2 Request an SSL certificate
We need an SSL certificate to enable HTTPS for the custom domain when it is accessed through CloudFront.
Follow the AWS docs at https://docs.aws.amazon.com/acm/latest/userguide/gs-acm-request-public.html to request a public certificate for your domain. Some important points:
*.example.comto the certificate.
- Use DNS validation (rather than email validation), whereby in the “pending validation” stage you can choose “Create record in Route 53” which saves time (since we have already configures Route 53 for this domain).
I encountered one caveat in this process:
To use an ACM Certificate with CloudFront, you must request or import the certificate in the US East (N. Virginia) region.
(from http://docs.aws.amazon.com/acm/latest/userguide/acm-services.html); i.e., change region to US East N. Virginia if needed (top right corner within the AWS interface).
3 Create a CloudFront distribution
Follow these AWS docs to create a CloudFront distribution: https://docs.aws.amazon.com/AmazonS3/latest/dev/website-hosting-cloudfront-walkthrough.html; unless a CloudFront distribution was already created by
s3_website (see one of the previous optional steps), in which case it needs to be merely edited (add the SSL certificate to it, and update “Alternate Domain Names” with
www.yourdomain.com if necessary).
Notice the designated CloudFront distribution domain, which should look similar to vtrlj8ubh2k69.cloudfront.net. Once set up the website should appear under it.
A few points I found noteworthy:
- One can choose to set HTTP to always redirect to HTTPS.
- Once issued, the SSL certificate can be selected from the drop down menu under “Custom SSL Certificates”.
- As pointed out in Vicky Lai’s blog post the “Origin” column in the CloudFront Console should show the S3 bucket’s endpoint
example.com.s3-website.us-east-1.amazonaws.com, and not the bucket name
s3_websitedoes this correctly). Note that when setting up, the drop down menu offers only the bucket name to be picked rather the correct endpoint; so, don’t use the drop down menu; type it in yourself.1
Update A records in Route 53, and update the
- In AWS go to Route 53 -> Hosted zones -> example.com
- For both A records, change the “Alias Target” from the S3 endpoint to the CloudFront distribution domain (i.e., something like
- If you use
s3_websitecheck or set the
s3_website.ymlto the correct distribution ID (something like
That’s it — the site should now be accessible under
It is not exactly clear to me what difference it makes to set the “Origin” to
example.com.s3-website.us-east-1.amazonaws.com. However, it solved one of my issues. At first I set the “Origin” value to the bucket name, similar to
example.com.s3.amazonaws.com, since that is what was offered by the drop down menu in CloudFront. The landing page of the website was working just fine under the custom domain. However, when I navigated to subdirectories in my domain, similar to
example.com/about/, the server did not seem to understand that it needed to look for the
aboutdirectory, and produced an error. Once I edited the “Origin” record to the S3 bucket endpoint, similar to
example.com.s3-website.us-east-1.amazonaws.com, all pages of the website started to display perfectly fine. ↩