Adeel NiazFull-Stack Developer | AWS | SEO Contact me

How to Deploy a Static Website on AWS S3 and CloudFront

A static site is just files: HTML, CSS, JavaScript and images. S3 stores them, and CloudFront serves them quickly over HTTPS from locations around the world. This guide covers the setup I use for this portfolio.

By Adeel Niaz. Published .

What you need

Step 1: Create a private S3 bucket

Create a bucket and leave Block all public access switched on. The bucket stays private, and only CloudFront will be allowed to read from it. Upload your files so that index.html sits at the root of the bucket.

Do not turn on S3 static website hosting for this setup. That feature needs a public bucket, and it is not used with origin access control.

Step 2: Create a CloudFront distribution

  1. Create a distribution and choose the S3 bucket as the origin.
  2. Under origin access, choose Origin access control (OAC). CloudFront then gives you a bucket policy to copy.
  3. Set the Default root object to index.html.
  4. Set the viewer protocol policy to Redirect HTTP to HTTPS and turn on automatic compression.

Paste the policy into the bucket permissions. It looks like this, with your own bucket name, account ID and distribution ID:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "AllowCloudFrontRead",
    "Effect": "Allow",
    "Principal": { "Service": "cloudfront.amazonaws.com" },
    "Action": "s3:GetObject",
    "Resource": "arn:aws:s3:::YOUR-BUCKET/*",
    "Condition": {
      "StringEquals": {
        "AWS:SourceArn": "arn:aws:cloudfront::YOUR-ACCOUNT-ID:distribution/YOUR-DISTRIBUTION-ID"
      }
    }
  }]
}

When the distribution finishes deploying, your site is live on its cloudfront.net address.

Step 3: Add your domain and HTTPS

  1. In AWS Certificate Manager, request a public certificate for your domain. For CloudFront it must be created in the US East (N. Virginia) us-east-1 region. Validate it with the DNS record ACM gives you.
  2. In the distribution settings, add your domain as an alternate domain name and select the certificate.
  3. In Route 53, create an A record with an alias to the distribution. Add an AAAA alias too if IPv6 is enabled.

Pick one version of your address, with or without www, and redirect the other to it. Otherwise search engines see two copies of the same site.

Step 4: Add a custom 404 page

Because the bucket is private, S3 answers a missing file with a 403 error rather than 404. In the distribution, create custom error responses for both 403 and 404 that return /404.html with the response code 404.

Step 5: Update the site

To publish changes, upload the files and clear CloudFront's cached copies:

aws s3 sync . s3://YOUR-BUCKET --exclude ".git/*"
aws cloudfront create-invalidation --distribution-id YOUR-DISTRIBUTION-ID --paths "/*"

For speed, give CSS and JavaScript a long Cache-Control lifetime and put a version in their URLs, such as style.css?v=2. Keep HTML on a short lifetime so visitors always get the current page.

A common trap: folder URLs

The default root object only applies to the home page. With a private S3 origin, a request for /services/ will not load /services/index.html. You have two options: use file-style URLs such as /services.html, or add a CloudFront Function that appends index.html to folder requests.

Final checklist

If you would rather have someone set this up for you, see my AWS cloud and deployment service.

Need help deploying your site?

Tell me about your project. Message me on WhatsApp or send an email.