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.

Relevant AWS docs: How to create a static website on AWS S3; How to use a custom domain with AWS S3; Setting up Amazon CloudFront; SSL certificate instructions.

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 (obviously replace with your own domain).
  • Follow the procedure given at 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, and follow the links from there.
  • Test the S3 website: Upload an index.html to the bucket (you can keep all options for the upload at their default values). Then go to (where you need to replace with the bucket name, and us-east-1 with your bucket’s region), and see if the contents of index.html show up.

Yay :laughing: we have a working website!! …without a custom domain or https yet :sweat_smile:

The www subdomain: Now prepare another S3 bucket for the subdomain “” to be later redirected to the root domain “” (btw, if you so wish, can be the main S3 bucket and the bucket can be configured to redirect — just swap their roles in this entire writeup):

  • Create a bucket named (all options can be left at their defaults; this bucket doesn’t need to be publicly readable).
  • Configure to redirect all requests to following Step 2.3 from the AWS docs at
  • Test the endpoints for the redirect by going to (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

Configuration with your domain name registrar:

  • In AWS go to Route 53 -> Hosted zones ->
  • 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 (and

:smile: 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 bucket and full permission to CloudFront, but does not have any further AWS permissions. A suitable IAM policy document can be found at: 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. The 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 to request a public certificate for your domain. Some important points:

  • Add and * to 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; 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:; 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 and if necessary).

Notice the designated CloudFront distribution domain, which should look similar to 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, and not the bucket name (btw s3_website does 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 s3_website configs:

  • In AWS go to Route 53 -> Hosted zones ->
  • 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_website check or set the cloudfront_distribution_id property in s3_website.yml to the correct distribution ID (something like SY9Q4DHIOUG7A)

That’s it — the site should now be accessible under and :tada: :tada: :tada:

  1. It is not exactly clear to me what difference it makes to set the “Origin” to vs However, it solved one of my issues. At first I set the “Origin” value to the bucket name, similar to, 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, the server did not seem to understand that it needed to look for the index.html within the about directory, and produced an error. Once I edited the “Origin” record to the S3 bucket endpoint, similar to, all pages of the website started to display perfectly fine.