Blog / Hosting a Static Site on AWS

Date
January 17, 2025
Tags

About

Amazon has several pages on this topic, but they don’t cover everything.

Goal

Have a static site with:

  • A custom domain name
  • Content stored in S3
  • Served using a CloudFront CDN
  • Secured by an auto-renewing SSL certificate

Your domain, example.com will be served from www.example.com. The root domain will redirect to the www subdomain.

This guide assumes you already have a domain registered with another host (e.g. GoDaddy, NameCheap). Or you can register directly with AWS in Route 53 and save yourself the name server steps.

Steps

  1. Set up domain in Route 53
  2. Create certificates
  3. Create S3 buckets
  4. Create CloudFront distributions
  5. Set Route 53 to point to CloudFront
  6. (optional) Create users
  7. (optional) Set up CI/CD action

1. Set up domain in Route 53

  1. Go to Route 53
  2. Click Create hosted zone
    • Domain name: Your root domain (e.g. example.com)
  3. Click Create hosted zone
  4. Take note of the row whose Type column contains NS. It will have a Value column with four records. Those are your four name servers for your domain host.
  5. Set those name servers at your domain registrar, unless you’re using AWS for domain registration.
Creating a hosted zone in Route 53

2. Create certificates

  1. Go to Certificate Manager
  2. Change the region (top right) to United States (N. Virginia, us-east-1)
  3. Click Request
  4. Select Request a public certificate and click Next
  5. On the Domain names page
    • Fully qualified domain name: Enter your domain name (e.g. example.com)
    • Click Add another name to this certificate
    • Enter *.example.com in the new field
    • Click Request
  6. In the Domains area, click the Create records in Route 53 button
  7. On the Create DNS records in Amazon Route 53 page, click Create records
Setting up a certificate

3. Create S3 buckets

  1. Go to S3
  2. Switch back to your preferred region. I like United States (Oregon, us-west-2)

www bucket

  1. Click Create bucket
    • Bucket name: www.example.com
    • Uncheck Block all public access
    • Check the I acknowledge box
    • Click Create bucket
  2. In that bucket, go to the Properties tab
  3. Scroll down to Static website hosting and click Edit
    • Select Enable
    • For Index document, enter index.html
    • Click Save changes
  4. Select that bucket, then go to the Permissions tab
  5. Scroll down to Bucket policy and click Edit
  6. Enter this JSON, changing the Resource value to the name of your www domain
    {
    	"Version": "2012-10-17",
    	"Statement": [
    		{
    			"Effect": "Allow",
    			"Principal": "*",
    			"Action": "s3:GetObject",
    			"Resource": "arn:aws:s3:::www.example.com/*"
    		}
    	]
    }
    
  7. Click Save changes

root bucket

  1. Go back to the list of buckets and click Create bucket
    • Bucket name: example.com
    • Uncheck Block all public access
    • Check the I acknowledge box
  2. Click Create bucket
  3. In that bucket, go to the Properties tab
  4. Scroll down to Static website hosting and click Edit
    • Select Enable
    • Select Redirect requests for an object
    • For Host name, enter www.example.com
    • Select https for Protocol
    • Click Save changes
  5. Go to the Permissions tab
  6. Scroll down to Bucket policy and click Edit
  7. Enter this JSON, changing the Resource value to the name of your root domain
    {
    	"Version": "2012-10-17",
    	"Statement": [
    		{
    			"Effect": "Allow",
    			"Principal": "*",
    			"Action": "s3:GetObject",
    			"Resource": "arn:aws:s3:::example.com/*"
    		}
    	]
    }
    
  8. Click Save changes
Setting the S3 bucket policy

4. Create CloudFront distributions

  1. Go to CloudFront

www distribution

  1. Click Create distribution
    • Select the Origin domain field
    • Select your www.example.com bucket
    • A yellow warning message about static site hosting should come up. Click the Use website endpoint button.
    • It will change your origin domain
      • from e.g. www.example.com.s3.us-west-2.amazonaws.com
      • to www.example.com.s3-website-us-west-2.amazonaws.com
  2. Fields to change for minimal cost:
    • Viewer protocol policy: Redirect HTTP to HTTPS
    • Web Application Firewall (WAF): Do not enable security protections
    • Price class: Use only North America and Europe
    • Alternate domain name (CNAME): Click Add item and enter www.example.com
    • Custom SSL certificate: Select your domain’s SSL certificate
  3. Click Create distribution

root distribution

  1. Click Create distribution
    • Select the Origin domain field
    • Select your example.com bucket
    • A yellow warning message about static site hosting should come up. Click the Use website endpoint button.
    • It will change your origin domain
      • from e.g. example.com.s3.us-west-2.amazonaws.com
      • to example.com.s3-website-us-west-2.amazonaws.com
  2. Fields to change for minimal cost:
    • Viewer protocol policy: Redirect HTTP to HTTPS
    • Web Application Firewall (WAF): Do not enable security protections
    • Price class: Use only North America and Europe
    • Alternate domain name (CNAME): Click Add item and enter www.example.com
    • Custom SSL certificate: Select your domain’s SSL certificate
  3. Click Create distribution
A CloudFront distribution

5. Set Route 53 to point to CloudFront

  1. Go to Route 53
  2. Select the hosted zone for your domain
  3. Click Create record
  4. Click Switch to wizard (it might be on that already)
  5. Select Simple routing and click Next

www subdomain

  1. Click Define simple record
    • subdomain: www
    • Record type: A
    • Value/Route traffic to, click Choose endpoint and type “cloud” then click on Alias to CloudFront distribution
    • Click on Choose distribution and select the only option (should be the www domain)
  2. Click Define simple record

root domain

  1. Click Define simple record
    • subdomain: (leave blank)
    • Record type: A
    • Value/Route traffic to, click Choose endpoint and type “cloud” then click on Alias to CloudFront distribution
    • Click on Choose distribution and select the only option (should be the root domain)
  2. Click Define simple record
  3. Click Create records (there should be two in the list)

That’s all you need to get your site running. To put content into your site, upload it to the www.example.com S3 bucket.


6. (optional) Create users

I use a GitLab pipeline to upload content to my S3 buckets. To do that, you need a user with a policy that enabled S3 content management and the ability to create CloudFront distribution cache invalidations.

  1. Go to IAM Policies
  2. Click Create policy
  3. Select the JSON toggle
  4. Enter the JSON below, changing the name of the buckets and the CloudFront distribution resource ARN
    {
    	"Version": "2012-10-17",
    	"Statement": [
    		{
    			"Sid": "S3Access",
    			"Effect": "Allow",
    			"Action": ["s3:PutObject", "s3:DeleteObject", "s3:GetObject", "s3:ListBucket"],
    			"Resource": ["arn:aws:s3:::www.example.com", "arn:aws:s3:::www.example.com/*"]
    		},
    		{
    			"Sid": "CloudFrontInvalidation",
    			"Effect": "Allow",
    			"Action": [
    				"cloudfront:CreateInvalidation",
    				"cloudfront:ListInvalidations",
    				"cloudfront:GetInvalidation"
    			],
    			"Resource": "arn:aws:cloudfront::1234567890:distribution/ABC123XYZ456"
    		}
    	]
    }
    
  5. Click Next
  6. Policy name: (any name you like)
  7. Click Create policy

That’s the policy, but it must be attached to a user.

  1. Go to IAM Users
  2. Click Create user
  3. User name: (any name you like, but I usually make mine the same as the policy name)
  4. Click Next
  5. Select Attach policies directly
  6. Search for and check the box of the policy you just created
  7. Click Next
  8. Click Create user

To run automated uploading, you need the access key and secret access key from that user.

  1. Select that user
  2. Click Create access key
  3. For Use case, select Other and click Next
  4. For Description tag value, give it a name describing where it will be used (e.g. GitHub, GitLab)
  5. Click Create access key
  6. The access key and secret access key will be shown. Copy them or download the CSV to save the values.

7. (optional) Set up CI/CD action

I use a private GitLab instance for my domains. This is the part of the .gitlab-ci.yml that runs my S3 upload and CloudFront cache invalidation.

Be sure to create the secrets to match your AWS variables. My Hugo build stores the content to be uploaded in a public folder. Yours may be different.

aws-s3:
    image: registry.gitlab.com/gitlab-org/cloud-deploy/aws-base:latest
    script:
        - aws configure set aws_access_key_id $AWS_ACCESS_KEY_ID
        - aws configure set aws_secret_access_key $AWS_SECRET_ACCESS_KEY
        - aws configure set default.region $AWS_DEFAULT_REGION
        - aws s3 sync ./public s3://$AWS_S3_BUCKET_NAME --delete

aws-cloudfront:
    image: registry.gitlab.com/gitlab-org/cloud-deploy/aws-base:latest
    script:
        - aws configure set aws_access_key_id $AWS_ACCESS_KEY_ID
        - aws configure set aws_secret_access_key $AWS_SECRET_ACCESS_KEY
        - aws cloudfront create-invalidation --distribution-id $AWS_CLOUDFRONT_DISTRIBUTION_ID --paths "/*"

Sample secrets:

  • AWS_ACCESS_KEY_ID: ACB123XYZ456
  • AWS_SECRET_ACCESS_KEY: ACB123XYZ456
  • AWS_DEFAULT_REGION: us-west-2
  • AWS_S3_BUCKET_NAME: www.example.com
  • AWS_CLOUDFRONT_DISTRIBUTION_ID: E999FGHJKL54321