You can deploy a static web app with a fully private S3 bucket by putting CloudFront in front of it and granting CloudFront exclusive access. The “legacy” style CloudFront distribution uses an Origin Access Identity (OAI) instead of the newer Origin Access Control (OAC).
- An AWS account with permissions for S3, CloudFront, and IAM.
- A built static website folder on your machine (for example containing
index.html, CSS, JS, and assets). - Optional but recommended: a registered domain (for example via Route 53) and an ACM certificate in
us-east-1if you want HTTPS on a custom domain.
- In the AWS console, go to S3 and choose “Create bucket”.
- Give it a DNS-compliant name (for example
my-static-site-bucket) and choose your region. - Under “Block Public Access”, leave Block all public access enabled (this keeps the bucket private).
- Complete creation with all other settings default (versioning/encryption optional).
- After creation, go to the bucket “Permissions” tab and confirm:
- “Block public access” is ON.
- No bucket policy is present yet.
- “Access control list (ACL)” does not grant public access.
- Open the bucket and click “Upload”.
- Add all your site files (HTML, CSS, JS, images, etc.) and start the upload.
- Ensure that at least an
index.htmlfile exists at the root of the bucket (or wherever you plan to point CloudFront’s default root object).
Note: You do not need to enable “Static website hosting” on the bucket when using CloudFront; CloudFront can simply read objects directly from S3.
- Go to the CloudFront console and open the “Origin access” or “Security” related section (exact UI label may vary slightly, look for “Origin access identity”).
- Create a new Origin Access Identity (OAI) and give it a recognizable name (for example
my-static-site-oai). - Copy or note the OAI identifier; it will be used when configuring the origin and bucket policy.
This OAI is a special “user” that CloudFront uses to fetch objects from your S3 bucket, while the bucket remains private to everyone else.
- In CloudFront, choose “Create distribution” and select Web (if asked).
- For Origin domain, choose your S3 bucket (the regular bucket endpoint, not the static website endpoint).
- Set Origin access to use the legacy OAI:
- Choose “Origin access identity (legacy)”.
- Select the OAI created earlier.
- Choose the option to automatically update the bucket policy if offered; otherwise you will add the policy manually in the next step.
- Ensure Viewer protocol policy is set to “Redirect HTTP to HTTPS” (or “HTTPS only”) to enforce HTTPS.
- Under Default cache behavior:
- Allowed HTTP methods: usually “GET, HEAD” is enough for a static site.
- Cache policy: you can start with the managed “CachingOptimized” policy.
- In Settings:
- Set Default root object to
index.html(or your main file). - If using a custom domain, enter it as an Alternate domain name (CNAME) and select your ACM certificate from
us-east-1.
- Set Default root object to
- Create the distribution and wait for it to deploy (this can take several minutes).
Once deployed, CloudFront will provide a domain name like d123456abcdef.cloudfront.net that you can use immediately to access the site.
If CloudFront did not auto‑update the bucket policy, add it yourself so that only the OAI can access objects:
- Go to S3 → your bucket → “Permissions” → “Bucket policy”.
- Add a policy of the following form (replace placeholders):
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCloudFrontOAIReadOnly",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity YOUR_OAI_ID"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::YOUR_BUCKET_NAME/*"
}
]
}- Save the policy.
- Confirm:
- The bucket still has Block public access enabled.
- There is no “Everyone (public access)” permission in the ACL.
Now the bucket is private; only CloudFront (via the OAI) can read your files.
- In your browser, open the CloudFront distribution domain, for example
https://d123456abcdef.cloudfront.net. - You should see your static site load.
- Test a direct S3 object URL (copy an object URL from the S3 console):
- It should now return an Access Denied error, confirming the bucket is private.
- The same file should load fine via the CloudFront URL.
If the CloudFront root URL returns an error:
- Check that the Default root object in the distribution is set correctly (
index.html). - Confirm that the file exists in the bucket path CloudFront expects.
- Verify the bucket policy uses the exact OAI ID of the distribution.
If you have a domain and want to access the app via something like https://www.example.com:
- Make sure you have an ACM certificate in
us-east-1for your domain (for examplewww.example.com). - In the CloudFront distribution:
- Add your domain under “Alternate domain names (CNAMEs)”.
- Attach the ACM certificate that matches the domain.
- In your DNS service (for example Route 53), create an A record for
www.example.com:- Type: A (or A – IPv4).
- Alias: Yes (if using Route 53).
- Target: your CloudFront distribution.
- After DNS propagates, visit
https://www.example.comand confirm the site loads via CloudFront.
By the end of these steps you have a private S3 bucket that only CloudFront (via a legacy OAI) can read and a CloudFront “legacy” Web distribution that serves your static web app over HTTPS. You can access it using the CloudFront URL or, optionally, your own custom domain.