How to Host a Static Website on S3 with CloudFront, HTTPS (ACM) and Route 53
In S3 Bucket Policy Examples we learned to lock buckets down. Now we open one carefully to the world: a static website on S3, then the production way with CloudFront, Origin Access Control (OAC), a free ACM certificate for HTTPS and a Route 53 alias for your domain.
What you'll learn in this chapter
- Why a static site does not need a server
- S3 website hosting: index, error page, website endpoint
- Why CloudFront + OAC beats a public bucket
- HTTPS with ACM (us-east-1) and a Route 53 alias
- Deploying from React, Angular, Vue, Hugo
1. A bakery story
A bakery wants a simple website: menu, photos, phone number. Renting a server means paying every hour, patching Linux and worrying about traffic on Diwali. But the pages never change per visitor. They are just files.
Files belong in S3. Put HTML, CSS, JS and images in a bucket and S3 serves them. Cost: a few rupees a month.
2. S3 static website hosting
Why do we need this?
A plain bucket returns files by exact key. Websites need "/" to show index.html and a friendly 404 page. Website hosting adds that.
What is it?
| Setting | Value | Why |
|---|---|---|
| Static website hosting | Enable | Turns on the website endpoint |
| Index document | index.html | Shown for / and for every folder |
| Error document | error.html | Shown on 404 |
| Block Public Access | Off (this bucket only) | The website endpoint has no signing; it needs public read |
| Bucket policy | Public GetObject (example 1 of the previous chapter) | Lets visitors read |
Endpoint for ap-south-1: http://bucket.s3-website.ap-south-1.amazonaws.com. Some older Regions use a dash, like s3-website-us-east-1. The website endpoint is HTTP only. That is the main reason we add CloudFront.
3. CloudFront with Origin Access Control
Why do we need this?
Without CloudFront: no HTTPS, the bucket must be public, and a visitor in London waits for Mumbai. CloudFront fixes all three.
What is it?
CloudFront is AWS's CDN with hundreds of edge locations that cache your files near users. OAC lets only your distribution read the bucket, so the bucket stays fully private with Block Public Access on.
| Point | S3 website endpoint | CloudFront + OAC |
|---|---|---|
| HTTPS | No | Yes, ACM free cert |
| Bucket public | Yes | No |
| Speed worldwide | One Region | Edge cache |
| Origin to choose | - | S3 REST endpoint (bucket.s3.ap-south-1.amazonaws.com), not the website endpoint |
Ravindra Bagale's Tip
OAC only works with the S3 REST endpoint. With OAC, set "Default root object" = index.html in CloudFront. Subfolder index pages need a CloudFront Function, or build tools that output folder/index.html links.
4. HTTPS with ACM and Route 53
Why do we need this?
Browsers mark HTTP sites "Not secure". ACM gives free public certificates that renew automatically.
What is it?
- The certificate for CloudFront must be requested in us-east-1 (N. Virginia), whatever Region your bucket is in.
- Validate by DNS: ACM gives a CNAME; with Route 53 one click creates it.
- Add the domain under "Alternate domain names (CNAMEs)" in CloudFront.
- In Route 53 create an A record, Alias = yes, target the distribution. Alias works at the root (bakery.in) where CNAME cannot, and alias queries to AWS targets are free.
5. Deploy from any stack
| Stack | Build | Upload |
|---|---|---|
| React (Vite) | npm run build → dist/ | aws s3 sync dist/ s3://b --delete |
| Angular | ng build → dist/app/browser | aws s3 sync dist/app/browser s3://b --delete |
| Vue | npm run build → dist/ | aws s3 sync dist/ s3://b --delete |
| Next.js (export) | output:"export", next build → out/ | aws s3 sync out/ s3://b --delete |
| Hugo | hugo → public/ | aws s3 sync public/ s3://b --delete |
| Plain HTML | - | aws s3 sync ./site s3://b |
After each deploy: aws cloudfront create-invalidation --distribution-id E123 --paths "/index.html". Why: CloudFront caches; invalidation fetches the new file. Hashed asset names (app.3f9a.js) avoid invalidating everything.
SPA routing (React Router): in CloudFront › Error pages, map 403 and 404 to /index.html with response 200.
6. Lab: part A website endpoint, part B CloudFront + OAC + HTTPS
How to do it (step by step)
Part A: S3 website
- Create files:
echo "<h1>Bakery</h1>" > index.html; echo "<h1>Not found</h1>" > error.html. - Create bucket
yourname-bakery-sitein ap-south-1. Uncheck Block all public access, tick the acknowledgement. aws s3 cp index.html s3://yourname-bakery-site/; aws s3 cp error.html s3://yourname-bakery-site/.- Properties › Static website hosting › Edit › Enable, Index = index.html, Error = error.html › Save.
- Permissions › Bucket policy: public GetObject on
arn:aws:s3:::yourname-bakery-site/*. curl http://yourname-bakery-site.s3-website.ap-south-1.amazonaws.com/. Expected: <h1>Bakery</h1>./nopegives Not found.
Part B: make it private behind CloudFront
- Delete the bucket policy, disable website hosting, turn Block all public access back on.
- (If you own a domain) ACM, Region us-east-1 › Request public certificate ›
www.yourdomain.com› DNS validation › Create records in Route 53. Wait for "Issued". - CloudFront › Create distribution › Origin domain =
yourname-bakery-site.s3.ap-south-1.amazonaws.com(not the website one). - Origin access = Origin access control settings › Create new OAC (Sign requests).
- Viewer protocol policy = Redirect HTTP to HTTPS. Default root object =
index.html. WAF = do not enable (lab). - (Domain) Alternate domain name =
www.yourdomain.com, Custom SSL certificate = the ACM cert. Create. - Copy the policy CloudFront shows and paste it into the bucket policy. It allows
cloudfront.amazonaws.comwithAWS:SourceArn= your distribution. - Wait for Deployed.
curl -I https://dxxxx.cloudfront.net/. Expected:HTTP/2 200,x-cacheheader. - Verify private:
curl -I https://yourname-bakery-site.s3.ap-south-1.amazonaws.com/index.html. Expected: 403. - (Domain) Route 53 › hosted zone › Create record › www, A, Alias on, Alias to CloudFront distribution. Open https://www.yourdomain.com.
Cleanup
- Delete the Route 53 record. CloudFront › Disable, wait, then Delete.
aws s3 rb s3://yourname-bakery-site --force. Delete the ACM cert if unused.
7. Interview questions
Q. Does the S3 website endpoint support HTTPS?
No, HTTP only. Use CloudFront for HTTPS.
Q. What is OAC?
Origin Access Control: CloudFront signs requests to S3 so only the distribution can read a private bucket. It replaces the older OAI.
Q. Which Region for a CloudFront ACM certificate?
us-east-1, always.
Q. Why alias and not CNAME for bakery.in?
A CNAME is not allowed at the zone apex; an alias A record is, and AWS alias queries are free.
Q. Users see the old page after deploy. Why?
CloudFront cache. Create an invalidation or use hashed file names.
Try at home
- Deploy a React build to S3 + CloudFront and fix deep links with the 403/404 → index.html rule.
- Compare curl -I timings from the website endpoint and CloudFront.
In short
- Static sites are files; S3 serves files.
- Website endpoint = HTTP only and needs a public bucket.
- CloudFront + OAC keeps the bucket private and adds HTTPS and caching.
- ACM cert for CloudFront lives in us-east-1.
- Route 53 alias A record points the domain at CloudFront.
Practice Tasks
Sahyadri Clinic is a healthcare business in Satara. Dr. Anil Deshmukh looks after its IT and cloud work. They count money in INR and run their AWS resources in the ap-south-1 Region. In the tasks below you do real jobs for them using what this lesson teaches. All names, account IDs, IP addresses and numbers are made up for practice.
0 / 12
Easy List distributions
Why: Find the distribution ID and domain name.
What to do: List all CloudFront distributions.
- Read the task and decide which AWS service it is about.
- Write aws, then the service name, then the operation (the action word).
- Add each needed flag (--name) followed by its value from the task.
- Flags can be in any order. Click Check answer.
Easy Read-only access to one bucket
Why: Least privilege: give only the actions and the bucket the job needs.
What to do: Write an identity policy that lets the user read objects (s3:GetObject) only in bucket sahyadriclinic-data.
- Start with { "Version": "2012-10-17", "Statement": [ ... ] }.
- In each statement write Effect, Action and Resource.
- Give only what the task needs (least privilege).
- Use double quotes and commas correctly. Click Check answer.
Medium Clear the CloudFront cache
Why: After a deploy, invalidate so visitors get the new files now, not after the TTL.
What to do: Invalidate all paths (/*) on distribution E2QWRUHAPOMQZL.
- Read the task and decide which AWS service it is about.
- Write aws, then the service name, then the operation (the action word).
- Add each needed flag (--name) followed by its value from the task.
- Flags can be in any order. Click Check answer.
Medium Bucket policy for a public website
Why: A static website bucket must let everyone read its files.
What to do: Write a bucket policy that lets anyone (*) do s3:GetObject on every object in sahyadriclinic-site.
- Start with { "Version": "2012-10-17", "Statement": [ ... ] }.
- In each statement write Effect, Action and Resource.
- Give only what the task needs (least privilege).
- Use double quotes and commas correctly. Click Check answer.
Medium Sync a website folder to S3
Why: sync uploads only new or changed files and can remove deleted ones.
What to do: Sync the local folder ./site to s3://sahyadriclinic-web/ and delete files in S3 that are no longer in ./site.
- Read the task and decide which AWS service it is about.
- Write aws, then the service name, then the operation (the action word).
- Add each needed flag (--name) followed by its value from the task.
- Flags can be in any order. Click Check answer.
Medium Turn a bucket into a website
Why: Static website hosting serves index.html and an error page straight from S3.
What to do: Enable static website hosting on s3://sahyadriclinic-web with index.html as the index page and error.html as the error page.
- Read the task and decide which AWS service it is about.
- Write aws, then the service name, then the operation (the action word).
- Add each needed flag (--name) followed by its value from the task.
- Flags can be in any order. Click Check answer.
Medium List and read one bucket
Why: ListBucket works on the bucket ARN; GetObject works on object ARNs.
What to do: Allow s3:ListBucket on bucket sahyadriclinic-data and s3:GetObject on all its objects. Nothing else.
- Start with { "Version": "2012-10-17", "Statement": [ ... ] }.
- In each statement write Effect, Action and Resource.
- Give only what the task needs (least privilege).
- Use double quotes and commas correctly. Click Check answer.
Hard Create a distribution from a config file
Why: The full config (origins, behaviors, certificate) is easiest to keep in JSON.
What to do: Create a CloudFront distribution from dist.json.
- Read the task and decide which AWS service it is about.
- Write aws, then the service name, then the operation (the action word).
- Add each needed flag (--name) followed by its value from the task.
- Flags can be in any order. Click Check answer.
Hard Bucket policy for CloudFront OAC
Why: With OAC, only your CloudFront distribution may read the private bucket.
What to do: Allow the service cloudfront.amazonaws.com to s3:GetObject on sahyadriclinic-site/*, only when aws:SourceArn equals arn:aws:cloudfront::102103063049:distribution/E2QWRUHAPOMQZL.
- Start with { "Version": "2012-10-17", "Statement": [ ... ] }.
- In each statement write Effect, Action and Resource.
- Give only what the task needs (least privilege).
- Use double quotes and commas correctly. Click Check answer.
Hard Request a free certificate for CloudFront
Why: CloudFront only uses ACM certificates from us-east-1. DNS validation renews automatically.
What to do: Request a certificate for sahyadriclinic.in and www.sahyadriclinic.in with DNS validation, in us-east-1.
- Read the task and decide which AWS service it is about.
- Write aws, then the service name, then the operation (the action word).
- Add each needed flag (--name) followed by its value from the task.
- Flags can be in any order. Click Check answer.
Hard Invalidate two paths
Why: Invalidating only what changed is cheaper and faster.
What to do: On E2QWRUHAPOMQZL, invalidate /index.html and /css/site.css.
- Read the task and decide which AWS service it is about.
- Write aws, then the service name, then the operation (the action word).
- Add each needed flag (--name) followed by its value from the task.
- Flags can be in any order. Click Check answer.
Hard Create an Origin Access Control
Why: OAC lets only CloudFront read your private S3 bucket.
What to do: Create an origin access control from oac.json.
- Read the task and decide which AWS service it is about.
- Write aws, then the service name, then the operation (the action word).
- Add each needed flag (--name) followed by its value from the task.
- Flags can be in any order. Click Check answer.
Revision, comparison and interview cards
Revise this lesson in a few minutes. Save the cards on your phone or download them as PNG files. The QR code on each card opens this lesson.






More cards: Ravi Sir's Cards.