Also see: Why There's a Trailing Slash, S3 access problem

This guide provides detailed instructions on setting up a static website using AWS S3, Route 53, and CloudFront, including enabling HTTPS. Involving several key steps, including S3 bucket setup, domain configuration, and implementing secure HTTPS connections.

S3 Hosting Only

if you're ok with that long long url, stop here.

Domain CNAME to S3

If you don't want an unattractive domain name:

Purchase a domain, set a CNAME record pointing to your S3 bucket's website endpoint

aws CNAME [your s3 endpoint]

if you're ok with adding CNAME to spec s3 endpoint everytime, stop here.

Using Route 53 (no HTTPS)

  1. Create a hosted zone in Route 53 name: aws.domain.com
  2. Add an A Record, set as an alias pointing to the S3 website endpoint You can select this from options AWS provides
    A - Routes traffic to an IPv4 address and some AWS resources
  3. Update NS records at your domain registrar to point to AWS name servers aws.domain.com to AWS Name Servers:
aws NS ns-464.awsdns-58.com
aws NS ns-723.awsdns-26.net
aws NS ns-1767.awsdns-28.co.uk
aws NS ns-1135.awsdns-13.org

if no HTTPS is ok for you. Skip next.

CloudFront grands you HTTPS

Step 1: AWS CloudFront

Step 2: Obtaining Let's Encrypt Certificate (optional)

ACM validation process is time-comsuming and need my domain email. I just choose lets encrypt to accelerate the process.

When adding SSL, there's a step to pause and add a TXT Record in Route53 (Note1)

brew install certbot
sudo certbot certonly --manual --preferred-challenges=dns -d aws.domain.com
# Some guidelines to review, keep saying y
# TXT record Please deploy a DNS TXT record under the name:
**_acme-challenge**.domain.com.
with the following value:
zA7PnMP...bAf9oUUdQT0

Upon success, it tells you files are generated in /etc/letsencrypt/live/aws.domain.com/. You'll need two files from this folder:

Step 3: Import Certificate to ACM

Step 4: CloudFront

  1. Choose the Certificate you just created
  2. Alternate domain name (CNAME): Add the domain you want protected by HTTPS (Note2)

Step 5: Route 53

Change your original domain.com A record from pointing to S3 resource to pointing to CloudFront

Certificates are valid for 90 days, so repeat steps 2 to 4 after expiration

CloudFront grands you HTTPS (another way)

If you don't want your S3 website endpoint to be directly accessible (http://your-bucket.s3-website-us-east-1.amazonaws.com/), even if you've already configured your own domain name with Route 53 and set up CloudFront, and you only want to access S3 through CloudFront while blocking all other access:

Step 1: Create OAC

In CloudFront > Origin access > Create Control Setting, simply create it. This step is straightforward.

Step 2: Add a Cloudfront Function

  1. You need to create a new CloudFront Function to handle the issue where accessing domain.com/ does not load the page, but instead needs to be domain.com/index.html when using OAC (Origin Access Control).
  2. function handler(event) {
      var request = event.request;
      if (request.uri === '/') {
          request.uri = '/index.html';
      } else if (request.uri.endsWith('/')) {
          request.uri += 'index.html';
      }
      return request;
    }
  3. Then go to Distribution > Behaviors tab > Edit > Function associations > Viewer request > CloudFront Function > Select Function ARN / Name to complete the binding. This means that now, if a request has /, the CloudFront Function will modify the request before sending it to S3, changing it to /index.html.
  4. But from your perspective, you won't be aware that CloudFront has modified the request.uri for you.

Step 3: Associate OAC to your Distribution Origin

  1. Go editing a Distribution > Origins Tab > Select Origin > Edit, for the Origin domain field, AWS suggests "For Origin domain, choose your S3 target. It suggests using the website endpoint." Ignore this suggestion and keep the simple S3 endpoint, as this allows you to set up Origin access in the next step
    When using S3 with CloudFront and OAC, the bucket needs to use the S3 object access endpoint rather than the static website endpoint. This is why you need step 2 to create a middleware-like function to handle this.
  2. For Origin access, select Origin access control settings (recommended) and associate it with the OAC you added in step 1.

Step 4: Rewrite Bucket Policy at Least Privilege

  1. Go back to S3 and rewrite the Bucket policy. Remove the potentially risky "allow all" policy and replace it with a policy that denies all access by default, only allowing access from a specific Distribution. This creates a whitelist.
  2. {
        "Version": "2008-10-17",
        "Id": "PolicyForCloudFrontPrivateContent",
        "Statement": [
            {
                "Sid": "AllowCloudFrontServicePrincipal",
                "Effect": "Allow",
                "Principal": {
                    "Service": "cloudfront.amazonaws.com"
                },
                "Action": "s3:GetObject",
                "Resource": "arn:aws:s3:::<your-bucket>/*",
                "Condition": {
                    "StringEquals": {
                        "AWS:SourceArn": "arn:aws:cloudfront::263386971159:distribution/<distribution id>"
                    }
                }
            }
        ]
    }

Remember: if you don't set up the Origin Access Control (OAC) for your bucket policy, even if you whitelist your CloudFront distribution, it will be meaningless. So, always remember to do step 1.

This configuration ensures that your S3 content can only be accessed through the specified CloudFront Distribution, providing enhanced security and access control.

Distribution Invalidation

What If: Your S3 bucket has been updated, but the changes are not being reflected when accessed through your domain.

To resolve this, you need to create an invalidation in CloudFront and specify that all files should be invalidated so that they are re-fetched.

read: AWS cloudfront not updating on update of files in S3

Notes & Some Thoughts

Note1

The TXT record in Step 3 should be set in Route53, not your domain provider's

Because your domain is pointing to AWS Name Server, it means this domain's resolution is handled by Route53

Note2

Although the domain is specified when adding an SSL certificate, in CloudFront Distribution you still need to explicitly state which domain needs HTTPS protection in Alternate domain name. If not specified in CloudFront Distribution, even with a complete SSL certificate, it won't protect you.

Thought 1

Regarding AWS resource configuration, you're often told to set the bucket name equal to the domain name, same for Route 53. Then you might wonder if one step goes wrong, will the whole setup fail?

Scenario: bucket name is different

  1. same name (Of course IT WORKS)
  2. subbbb.aws.domain.com
    # point to
    subbbb.aws.domain.com.s3-websit…
  3. Try using different name (FAILED)
    anothersub.aws.domain.com
    # point to
    subbbb.aws.domain.com.s3-websit…
    1. Can't find S3 resource when creating. AWS frontend automatically matches your input to find matching S3 buckets.
    2. HACK: First fill in the subdomain same as S3, it will automatically find the S3 bucket with the same name. After selection, change the name from subbbb to anothersub. Even though the alias is successful, accessing the URL still fails.
      # 404 Not Found
      - Code: NoSuchBucket
      - Message: The specified bucket does not exist
      - BucketName: **anothersub**.domain.com
      - RequestId: 6MTX7N8Z7KNGPBNP
      - HostId: JPiP....mofF7c=

While DNS resolves anothersub.aws.jialin00.com to the S3 endpoint of subbbb.aws.domain.com.s3-websit…, the S3 service itself remains unaware of this DNS mapping. In AWS, DNS and S3 storage are decoupled. This means they work separately, even when connected.

Thought 2

What If: Route 53 Hosted Zone is set as domain.com, and inside it has aws.domain.com A record Alias to CloudFront or S3 Resource endpoint. If domain.com is currently hosted elsewhere like Github, will it affect the entire configuration?

Truth is: Your domain.com in the domain provider DNS already has an A record to Github Name Server IP. In this sense, Route53 having domain.com won't take over. The domain provider DNS points to Github, and won't be taken over by AWS Route53's Hosted Zone having the same name for domain.com resolution.

So If your Route53 Hosted Zone is aws.domain.com, it won't care about notaws.domain.com or any other subdomains of domain.com. Route 53 only mind its own business.