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.
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.htmlat 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
- 1Create a private bucket
- 2Upload the site
- 3Add Origin Access Control
- 4Create the distribution
- 5Set the bucket policy
- 6Invalidate the cache