Skip to main content

How to host a static site on Amazon S3 with CloudFront

Host a static website on a private S3 bucket fronted by CloudFront with HTTPS and Origin Access Control. Covers upload, distribution setup, and cache invalidation.

Difficulty
Beginner
Duration
40 minutes
Steps
6

A static site, plain HTML, CSS, and JavaScript, can be hosted cheaply and reliably on Amazon S3. Putting CloudFront, AWS's content delivery network, in front adds HTTPS, global edge caching, and lower latency. The modern pattern keeps the bucket fully private and lets only CloudFront read it via Origin Access Control (OAC).

Prerequisites

  • An AWS account and the AWS CLI configured.
  • A built static site (an index.html at minimum).

Steps

1. Create a private bucket

aws s3api create-bucket --bucket my-site-assets --region us-east-1
aws s3api put-public-access-block --bucket my-site-assets \
  --public-access-block-configuration BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true

Keep the bucket private. CloudFront, not the public, reads from it.

2. Upload the site

aws s3 sync ./dist s3://my-site-assets --delete

3. Add Origin Access Control

Create an OAC so CloudFront signs requests to S3. In Terraform:

resource "aws_cloudfront_origin_access_control" "site" {
  name                              = "site-oac"
  origin_access_control_origin_type = "s3"
  signing_behavior                  = "always"
  signing_protocol                  = "sigv4"
}

4. Create the distribution

Point the distribution's origin at the bucket's regional domain, attach the OAC, set the default root object to index.html, and redirect HTTP to HTTPS. Use the default CloudFront certificate or attach an ACM certificate for a custom domain (the certificate must be in us-east-1).

5. Set the bucket policy

Allow only the distribution to read:

{
  "Effect": "Allow",
  "Principal": { "Service": "cloudfront.amazonaws.com" },
  "Action": "s3:GetObject",
  "Resource": "arn:aws:s3:::my-site-assets/*",
  "Condition": { "StringEquals": { "AWS:SourceArn": "<distribution-arn>" } }
}

6. Invalidate the cache

After each deploy, refresh edge caches:

aws cloudfront create-invalidation --distribution-id <id> --paths "/*"

Verification

Open the CloudFront domain in a browser over HTTPS. Inspect response headers for x-cache: Hit from cloudfront on a second load. Confirm the S3 website URL itself returns Access Denied, proving the bucket is private.

Next Steps

Add a custom domain via Route 53 and ACM, set cache-control headers per file type, and automate sync plus invalidation in your CI pipeline.

Prerequisites

  • AWS account
  • Built static site files
  • AWS CLI configured

Steps

  • 1
    Create a private bucket
  • 2
    Upload the site
  • 3
    Add Origin Access Control
  • 4
    Create the distribution
  • 5
    Set the bucket policy
  • 6
    Invalidate the cache

Category

Cloud