Setup a Static Site on AWS with S3 and Cloudfront

This is pretty well-trodden territory, but I have recently setup several static sites on AWS, using S3 + Cloudfront, and I thought it would be nice to document the process. The setup isn't hard, but there are lots of steps, and you have to do most them in the right order.

S3 can host static sites by itself. The problem comes if you want:

It's 2020, so you really gotta have HTTPS.

And to do that, you need to bring in two other services:

Step 1 - Setup Route 53

Start by registering the domain, and then getting the domain itself setup inside of AWS.

  1. Buy a domain name. It's possible to buy a domain through AWS, but common wisdom is to separate your domain registrar from your hosting.

  2. Create a new Hosted Zone in Route 53

  3. Create a new NS record. There should be 4 nameserver records. Copy these values, and set them as the nameservers with your domain registrar. The exact process varies by registrar, but they all should have some way to set custom nameservers.

Step 2 - Setup ACM

Now that the domain is setup, it is possible to issue a certificate.

  1. Next, go to Certificate Manager

  2. Create a new public certificate

  3. Add one domain name for example.com (substitute your own domain here)

  4. Click "next", then DNS Validation

  5. Skip "add tags" (tagging is a best practice, but not required)

  6. Click "confirm and request". You should now see your new certificate in "pending" status.

  7. Click the arrow, to the left of the domain name, to expand the table row.

  8. Below, in the status section, again click an arrow to expand the "domain" table.

  9. Click the blue button labeled "Create Record in Route 53".

It usually takes 10-20 minutes for this certificate to change from "pending" to "verified"

Step 3 - Setup S3

Next, setup the S3 bucket. This is where the actual files will be hosted.

  1. Go to S3

  2. Create a new bucket, something like example-com. S3 bucket names are globally unique, so you need to pick a name that nobody else has ever chosen. This name can be anything - it does not need to match the public domain name of the website.

  3. Disable the checkbox that says "Block All Public Access". Because this bucket is intended to host our static website, we want to allow public access.

  4. Review the other options, but you can probably leave the defaults as-is.

  5. Click "Create Bucket" and return to the list of all buckets

  6. Click the name of your new bucket to bring up the bucket settings

  7. Choose the "Permissions" tab, and set the following "Bucket Policy"

Be sure to replace YOUR_BUCKET_NAME with.. your bucket name. This policy allows everybody to read the contents of the bucket, so make sure you don't upload anything sensitive.

{
    "Version": "2008-10-17",
    "Id": "Policy20201104",
    "Statement": [
        {
            "Sid": "Statement20201104",
            "Effect": "Allow",
            "Principal": {
                "AWS": "*"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::YOUR_BUCKET_NAME/*"
        }
    ]
}

Save the changes

Step 4 - Setup Cloudfront

Next, setup Cloudfront to use the S3 Bucket and Certificate that were created in earlier steps.

  1. Go to Cloudfront

  2. Create a new Distribution

  3. Choose Web Distribution

  4. For "Origin Domain Name", choose your S3 bucket from the dropdown list

  5. Skip "Origin Path"

  6. Skip "Origin ID"

  7. Skip "Custom Origin Headers"

  8. Set "Viewer Policy Protocol" to Redirect HTTP to HTTPS

  9. Skip pretty much everything else..

  10. Set "Compress Objects Automatically" to "Yes"

  11. Set "Alternative Domain Names (CNAME)" to your domain name, ex: example.com

  12. Set "SSL Certificate" to "Custom SSL Certificate". You should be able to choose the certificate created in Step 2 (above) from the dropdown list

  13. Set "Default Root Object" to "index.html" (do NOT include a / here)

  14. Ensure "Distribution State" is "Enabled"

  15. Click "Save"

Step 5 - Finish Setting up Route 53

Finally, finish the domain name setup, by pointing the domain name to Cloudfront.

  1. Go back to Route 53

  2. Choose the Hosted Zone, which was setup previously in Step 1

  3. Click "Create Record"

  4. Choose "Simple Routing"

  5. Click "Define Simple Record"

  6. Leave the "record name" field blank

  7. Under "Value/Route Traffic To", choose "Alias to Cloudfront Distribution"

  8. Choose the "us-east-1" (at the time of writing, ALL Cloudfront Distributions are implicitly in us-east-1)

  9. Choose the ID of your distribution

  10. Set "Record Type" to "A Record"

  11. Click "Define Simple Record"

  12. Click "Create Records" to finish

Step 6 - Upload Content to S3

There are several ways to upload content to S3. One option is to upload files individually by drag-n-drop.

Another option is to upload using the comand line tools. Assuming you already have installed and configured the AWS command line tools, this command will upload the contents of my-local-dir to an S3 bucket named my-bucket.

aws s3 sync my-local-dir/ s3://my-bucket --acl public-read

If the command is successful, you should see output which indictes the files that have been uploaded.

If the command fails, some typical issues to check include:

Finally, you may want to invalidate Cloudfront, which can also be done via the CLI. Be sure to replace YOUR_DISTRIBUTION_ID with the unique ID of the Cloudfront Distribution, which can be found in the AWS Cloudfront UI.

aws cloudfront create-invalidation --distribution-id YOUR_DISTRIBUTION_ID --paths '/*'

Make sure that you upload a file called "index.html" to S3. This is the "default" file that will be loaded when visitors come to your homepage, without deep-linking to a specific page on the site.

Step 7 - Check if it worked

Now, if everything worked, you should be able to visit your website!

If it DIDN'T work, you'll need to review each step and analyze any error messages from AWS.

The key question is - where is the error originating?

Each Cloudfront Distribution has it's own domain name, something like *****.cloudfront.net. If visiting that URL works, but your own domain name doesn't work, then you've got a DNS problem, either in Cloudfront or Route 53.

If the Cloudfront domain name doesn't work, you can try visiting the S3 URL directly. Use a private browser, so that you'll see the same thing as a non-logged-in user. Each S3 bucket and object within a bucket has a unique domain name, something like https://******.s3.us-east-1.amazonaws.com/index.html. If you are able to access content directly from S3, but cannot access it from Cloudfront, that means you've got a Cloudfront problem.

If you cannot access the content at all, even directly on S3, then you've probably got a permissions problem. Check your "Bucket Policy" and make sure that items within the bucket are set to "Public Read".