Setup a Static Site on AWS with S3 and Cloudfront
2020-11-04This 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:
- a custom domain name
- with HTTPS
It's 2020, so you really gotta have HTTPS.
And to do that, you need to bring in two other services:
- Cloudfront
- Certificate Manager
Step 1 - Setup Route 53
Start by registering the domain, and then getting the domain itself setup inside of AWS.
-
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.
-
Create a new
Hosted Zone
in Route 53 -
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.
-
Next, go to Certificate Manager
-
Create a new
public certificate
-
Add one domain name for
example.com
(substitute your own domain here) -
Click "next", then
DNS Validation
-
Skip "add tags" (tagging is a best practice, but not required)
-
Click "confirm and request". You should now see your new certificate in "pending" status.
-
Click the arrow, to the left of the domain name, to expand the table row.
-
Below, in the status section, again click an arrow to expand the "domain" table.
-
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.
-
Go to S3
-
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. -
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.
-
Review the other options, but you can probably leave the defaults as-is.
-
Click "Create Bucket" and return to the list of all buckets
-
Click the name of your new bucket to bring up the bucket settings
-
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.
-
Go to Cloudfront
-
Create a new
Distribution
-
Choose
Web Distribution
-
For "Origin Domain Name", choose your S3 bucket from the dropdown list
-
Skip "Origin Path"
-
Skip "Origin ID"
-
Skip "Custom Origin Headers"
-
Set "Viewer Policy Protocol" to
Redirect HTTP to HTTPS
-
Skip pretty much everything else..
-
Set "Compress Objects Automatically" to "Yes"
-
Set "Alternative Domain Names (CNAME)" to your domain name, ex:
example.com
-
Set "SSL Certificate" to "Custom SSL Certificate". You should be able to choose the certificate created in Step 2 (above) from the dropdown list
-
Set "Default Root Object" to "index.html" (do NOT include a
/
here) -
Ensure "Distribution State" is "Enabled"
-
Click "Save"
Step 5 - Finish Setting up Route 53
Finally, finish the domain name setup, by pointing the domain name to Cloudfront.
-
Go back to Route 53
-
Choose the Hosted Zone, which was setup previously in Step 1
-
Click "Create Record"
-
Choose "Simple Routing"
-
Click "Define Simple Record"
-
Leave the "record name" field blank
-
Under "Value/Route Traffic To", choose "Alias to Cloudfront Distribution"
-
Choose the "us-east-1" (at the time of writing, ALL Cloudfront Distributions are implicitly in us-east-1)
-
Choose the ID of your distribution
-
Set "Record Type" to "A Record"
-
Click "Define Simple Record"
-
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:
-
AWS CLI tools not installed
-
AWS CLI tools installed, but not configured with valid AWS credentials
-
Insufficient permissions (credentials exist, but user doesn't have permission to upload to S3)
-
Wrong local folder name
-
Wrong S3 bucket name
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?
- DNS?
- Cloudfront?
- S3?
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".