Host a Static Website on S3 + CloudFront
Host a private S3 website delivered globally over HTTPS via CloudFront.
Introduction
30 Second Summary
Every website you visit loads from a server somewhere in the world. The closer that server is to you, the faster the page appears.
In this project, you will host a personal webpage on AWS using S3 for file storage and CloudFront to deliver it globally over HTTPS.
What You'll Build
You'll paste a single CloudFront URL into any browser and watch your personal webpage load instantly, served securely from the nearest AWS edge location to whoever visits it.
By the end of this project, you'll have:
- A live website you can share with anyone by pasting a secure HTTPS link into any browser.
- A private S3 bucket that stores your website files where nobody can access them directly.
- A CloudFront distribution delivering your site globally through a CDN with automatic HTTPS encryption.
- Secret Mission: Add a custom 404 error page so visitors see a friendly message instead of a generic XML error.
Are there any prerequisites?
You'll need an AWS account to complete this project. If you don't have one yet, head to aws.amazon.com and create a free account before starting.
Basic familiarity with HTML and CSS is helpful, but you won't need to write code from scratch. All code is provided for you to paste.
Before We Start
Before we dive in, let's lock in what you're about to build and why it matters to you.
Set Up Your Website Files
Before you can deploy anything to AWS, you need a webpage to deploy. Having files ready on your computer means the rest of this project can focus purely on cloud infrastructure.
In this step, you will create a project folder and build a simple personal webpage with HTML and CSS. Once it looks good locally, you will know it is ready to upload to the cloud.
In this step, get ready to:
- Create a project folder on your Desktop with your website files inside.
- Write a styled personal webpage using starter HTML and CSS.
- Verify the page renders correctly in your browser before uploading to AWS.
Create your project folder and HTML file
You need a dedicated folder to keep your website files together. You will create this folder on your Desktop so it is easy to find later.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Terminal (macOS) or Command Prompt (Windows) and press Enter to open it.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir my-aws-website
cd my-aws-website
- Confirm the folder exists by running:
ls
The output will be empty because the folder is new. That is expected.
Now you need a text editor to create your HTML file. You can use any editor you are comfortable with.
- Open your text editor (VS Code, Notepad, or any editor you prefer).
- Create a new file and save it as index.html inside the my-aws-website folder on your Desktop.
- Paste the following HTML code into index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My AWS-Hosted Website</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<h1>Hello, I'm hosting on AWS!</h1>
<p>This website is served from an S3 bucket through CloudFront's global CDN. The bucket is completely private, and CloudFront handles all public access securely over HTTPS.</p>
<div class="card">
<h2>What I learned</h2>
<ul>
<li>Created a private S3 bucket for storage</li>
<li>Set up CloudFront with Origin Access Control</li>
<li>Configured a bucket policy for secure access</li>
<li>Deployed a site with HTTPS and global CDN</li>
</ul>
</div>
<footer>
<p>Hosted with Amazon S3 + CloudFront</p>
</footer>
</div>
</body>
</html>
What does this HTML do?
This is a simple personal webpage with a heading, a short description, and a card listing what you will learn in this project.
The <link rel="stylesheet" href="style.css"> tag connects the page to a separate CSS file you will create next. Without it, the page would display as unstyled plain text.
- Save index.html.
- Open index.html in your browser by double-clicking the file in Finder (macOS) or File Explorer (Windows).
You should see a plain white page with unstyled black text. The heading, paragraph, and list are there, but they have no visual styling yet.
Seeing a blank page or raw code?
- Make sure the file is saved with the .html extension, not .html.txt. Some editors add .txt by default.
- Confirm you are opening the file in a browser, not a text editor. Right-click the file and choose Open with your browser if needed.
Still stuck? Help me open my index.html file in the browser
Style your page with CSS
The unstyled page works, but it does not look like a real website yet. You will add a stylesheet that gives it a dark gradient background, centered layout, and polished card design.
- In your text editor, create a new file and save it as style.css in the same my-aws-website folder (next to index.html).
- Paste the following CSS into style.css to set up the page layout:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
color: #e0e0e0;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
}
.container {
max-width: 600px;
text-align: center;
}
What does this CSS do?
- The * selector resets browser defaults so spacing is consistent.
- The body styles create a dark blue gradient background and center all content vertically and horizontally.
- The .container limits the content width so it does not stretch across the full screen.
- Save style.css and refresh your browser tab showing index.html.
You should see a dark blue gradient background with light-coloured text centered on the page.
Still seeing a plain white page?
- Confirm style.css is saved in the same folder as index.html. The <link> tag in your HTML looks for style.css in the same directory.
- Check that the file is named exactly style.css (not styles.css or style.css.txt).
Need help? My CSS file is not loading in the browser
- Add the typography and card styles below your existing CSS in style.css:
h1 {
font-size: 2.5rem;
margin-bottom: 1rem;
color: #ffffff;
}
p {
font-size: 1.1rem;
line-height: 1.6;
margin-bottom: 2rem;
color: #b0b0b0;
}
.card {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 12px;
padding: 2rem;
margin-bottom: 2rem;
text-align: left;
}
.card h2 {
font-size: 1.3rem;
margin-bottom: 1rem;
color: #ffffff;
}
What does this CSS do?
- The h1 and p rules set font sizes and colours for the heading and body text.
- The .card styles create a subtle glass-like box with a rounded border and padding.
- The .card h2 styles the card title so it stands out as white text.
- Save style.css and refresh your browser.
You should now see a large white heading, a grey paragraph, and a card box with a semi-transparent border.
- Add the final styles for the list items and footer below your existing CSS in style.css:
.card ul {
list-style: none;
padding: 0;
}
.card li {
padding: 0.5rem 0;
padding-left: 1.5rem;
position: relative;
color: #b0b0b0;
}
.card li::before {
content: ">";
position: absolute;
left: 0;
color: #4fc3f7;
}
footer {
color: #666;
font-size: 0.9rem;
}
What does this CSS do?
- The .card ul removes the default bullet points from the list.
- The .card li::before adds a cyan > character before each list item, giving it a terminal-style look.
- The footer styles the bottom text in a subtle dark grey so it does not compete with the main content.
Before you refresh, what do you think the list items will look like now that you have replaced the default bullets?
- Save style.css and refresh your browser.
You should see your complete styled webpage. Each list item has a cyan > marker, and the footer text sits in subtle grey at the bottom.
List items still showing default bullets?
- Make sure you pasted the code below the existing CSS, not replacing it. Your file should contain all three chunks in order.
- Check for missing closing braces }. A missing brace in an earlier rule will break everything below it.
Still stuck? My CSS list styles are not working
✔️ Awesome, I've got everything!
Great. Double check that you have saved style.css with all three sections of CSS.
ⓧ I'd like to double check the full code
Your style.css should contain all three sections you pasted, in this order:
- Section 1: The * reset, body layout, and .container width.
- Section 2: The h1, p, .card, and .card h2 styles.
- Section 3: The .card ul, .card li, .card li::before, and footer styles.
Scroll up and confirm each section is present in your file, in order, with no missing closing braces.
Your index.html should match this exactly:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My AWS-Hosted Website</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<h1>Hello, I'm hosting on AWS!</h1>
<p>This website is served from an S3 bucket through CloudFront's global CDN. The bucket is completely private, and CloudFront handles all public access securely over HTTPS.</p>
<div class="card">
<h2>What I learned</h2>
<ul>
<li>Created a private S3 bucket for storage</li>
<li>Set up CloudFront with Origin Access Control</li>
<li>Configured a bucket policy for secure access</li>
<li>Deployed a site with HTTPS and global CDN</li>
</ul>
</div>
<footer>
<p>Hosted with Amazon S3 + CloudFront</p>
</footer>
</div>
</body>
</html>
Your website files are ready. Next up, you will upload them to Amazon S3 and discover why a private bucket alone is not enough to serve a website.
Create an S3 Bucket and Upload Your Site
Your website looks great on your own computer. Now it's time to put those files somewhere the rest of the world can reach them.
Amazon S3 (Simple Storage Service) is AWS's file storage service. You'll upload your HTML and CSS to an S3 bucket. But S3 keeps everything private by default. Let's see what that means in practice.
In this step, get ready to:
- Create a private S3 bucket in the AWS Console.
- Upload your index.html and style.css files to the bucket.
- Test whether your site is accessible from the bucket's URL.
Create your S3 bucket
An S3 bucket is a container that holds your files in the cloud. Each bucket needs a globally unique name across all of AWS.
- Sign in to the AWS Management Console at console.aws.amazon.com.
- In the search bar at the top, type S3 and click on S3 in the results.
- Click Create bucket.
- In the Bucket name field, enter a unique name like my-website-yourname-2026 (replace with your actual name).
- Confirm that Block all public access is checked. This is the secure default and should already be selected.
- Leave all other settings as defaults.
- Scroll to the bottom and click Create bucket.
You should see a green success banner at the top, and your new bucket now appears in the bucket list.
Why does the bucket name need to be unique?
S3 bucket names are globally unique across all AWS accounts worldwide. No two buckets anywhere on AWS can share the same name. That's why including your name and the year helps avoid collisions.
Seeing a "Bucket already exists" error?
Someone else already has that bucket name. Try adding more characters to make it unique. For example, add extra numbers or your last name.
Help me pick a valid S3 bucket name that's unique.
Upload your website files
- Click on your new bucket's name in the bucket list to open it.
- Click Upload.
- Click Add files.
- Navigate to your my-aws-website folder on your Desktop and select both index.html and style.css.
- Click Upload at the bottom of the page.
You should see a green success banner confirming both files uploaded. Click Close to return to your bucket. Both index.html and style.css now appear in the objects list.
Don't see both files listed?
Make sure you selected both files during upload. If only one appears, click Upload again and add the missing file.
Help me upload files to my S3 bucket.
Test your site's accessibility
Your files are now in the cloud. Let's see if the outside world can actually reach them.
- Click on index.html in your bucket's object list.
- In the object details page, find the Object URL field and copy the URL.
Before you paste this URL into a new browser tab, take a guess. Do you think your webpage will load, or will something else happen?
- Paste the Object URL into a new browser tab and press Enter.
You see an XML error page containing AccessDenied. Your webpage does NOT load. This is exactly what should happen.
Why am I getting AccessDenied?
You left Block all public access enabled on your bucket. This is the AWS-recommended security setting. It means nobody on the internet can directly access your S3 files through the Object URL.
This is a good thing. You don't want your S3 bucket exposed to the public internet. In the next step, you'll set up a secure way to serve these files without ever making the bucket public.
Your website files are safely stored in S3, but locked away from the public. In the next step, you'll put a delivery layer called CloudFront in front of your bucket to serve your site securely over HTTPS.
Create a CloudFront Distribution with OAC
In the previous step, you hit an AccessDenied error when trying to view your website directly from S3. That error proves your private bucket cannot serve a website on its own.
CloudFront is AWS's content delivery network (CDN). It sits in front of your private S3 bucket and serves your files to visitors over HTTPS, caching them at edge locations around the world. Your bucket stays completely private while CloudFront handles all public access.
In this step, get ready to:
- Create a CloudFront distribution connected to your S3 bucket.
- Set up Origin Access Control so only CloudFront can read your files.
- Update your bucket policy to grant CloudFront access and see your site go live.
Create a distribution with Origin Access Control
Origin Access Control (OAC) is AWS's way of letting CloudFront read your private S3 bucket without making it public. You'll configure this as part of creating your CloudFront distribution.
- In the AWS Console search bar at the top of the page, type CloudFront.
- Click CloudFront from the search results.
- Click Create distribution.
- In the Origin domain dropdown, select your S3 bucket.
- Under Origin access, select Origin access control settings (recommended).
- Click Create new OAC.
- Leave the defaults and click Create.
What does OAC do?
OAC creates a secure, identity-based connection between CloudFront and your S3 bucket. Without it, CloudFront has no permission to read your private files.
With OAC, CloudFront proves its identity to S3 using a signed request. S3 then checks the bucket policy and grants access only to that specific CloudFront distribution.
Configure settings and create the distribution
- In the Web Application Firewall (WAF) section, select Do not enable security protections.
- Scroll down to the Default root object field.
- Type index.html.
- Leave all other settings as defaults.
- Click Create distribution.
A blue banner appears at the top of the page telling you the S3 bucket policy needs to be updated.
- Click Copy policy to save the auto-generated bucket policy JSON to your clipboard.
Why disable WAF?
WAF (Web Application Firewall) adds security protection but costs money beyond the free tier. For a personal static site, the CloudFront Flat-Rate Free plan covers everything you need without WAF enabled.
What is the default root object?
When someone visits your CloudFront URL without specifying a file path, CloudFront needs to know which file to serve. Setting it to index.html means visitors see your homepage automatically.
Update the bucket policy and go live
The policy you copied tells S3 to allow read access from your specific CloudFront distribution. Without it, CloudFront would also get AccessDenied.
- Open your S3 bucket in a new browser tab.
- Click the Permissions tab.
- Scroll down to the Bucket policy section.
- Click Edit.
- Paste the policy JSON you copied from CloudFront.
- Click Save changes.
Your bucket now grants read access exclusively to your CloudFront distribution. Switch back to your CloudFront distribution tab and wait for the Status to change from "Deploying" to a date/timestamp. This takes 2-5 minutes.
While you wait
CloudFront is copying your distribution configuration to edge locations around the world. You can refresh the page to check the status.
Before you test, what do you think you'll see when you paste the CloudFront URL into your browser?
- Copy the Distribution domain name (it ends in .cloudfront.net).
- Paste it into a new browser tab and press Enter.
Your styled webpage loads over HTTPS. The same page you saw locally is now live on the internet, served securely through CloudFront's global CDN.
Page not loading?
- If you see "This site can't be reached" or a timeout, your distribution is still deploying. Wait another minute, refresh the CloudFront console page, and check the status column.
- If you see an AccessDenied XML error, the bucket policy was not saved correctly. Go back to your S3 bucket's Permissions tab and confirm the policy JSON is pasted in the Bucket policy editor.
- If you see a "NoSuchKey" error, the Default root object was not set. In your CloudFront distribution, click Settings, then Edit, and enter index.html in the Default root object field.
Still stuck? Help me troubleshoot why my CloudFront distribution isn't serving my S3 website.
Your website is live and accessible to anyone in the world via a secure HTTPS link. Next up, you'll learn how to update your site and see changes go live through the full deployment cycle.
Update Your Site and See Changes Live
Your website is live and accessible to anyone with the CloudFront URL. That's a great milestone.
But a website you can never change is just a frozen snapshot. In this step, you'll prove you understand the full deployment cycle: edit locally, re-upload to S3, invalidate the CloudFront cache, and see your changes go live.
In this step, get ready to:
- Edit your local HTML file with new heading text.
- Re-upload the updated file to your S3 bucket.
- Invalidate the CloudFront cache and verify the change is live.
Edit your heading text locally
CloudFront serves cached copies of your files from edge locations around the world. To see how updates work, you'll make a visible change to your local file first.
- Open the index.html file inside your my-aws-website folder using any text editor (Notepad, VS Code, or TextEdit).
- Find this line near the top of the body:
<h1>Hello, I'm hosting on AWS!</h1>
- Replace that line with a new heading, so it looks like this:
<h1>Welcome to my AWS-hosted site!</h1>
- Save the file.
- Double-click index.html to open it in your browser. You should see your new heading text displayed on the page.
Still seeing the old heading?
Make sure you saved the file after editing. If you're using TextEdit on macOS, ensure you're editing in plain text mode (Format > Make Plain Text).
Still stuck? Help me figure out why my HTML changes aren't showing in the browser.
Re-upload the updated file to S3
Your local file now shows the new heading. The version stored in S3 still has the old one. Time to sync them.
- Switch back to the AWS Console tab with your S3 bucket open.
- Click Upload.
- Click Add files and select your updated index.html from the my-aws-website folder.
- Click Upload to confirm. S3 overwrites the previous version of the file.
- You should see a green "Upload succeeded" banner at the top of the page.
Why does S3 let you overwrite without warning?
S3 uses the filename as the object key. Uploading a file with the same name replaces the stored object entirely. There's no version conflict dialog because S3 treats each upload as the definitive new version of that key.
Don't see the Upload button?
Make sure you're inside your bucket (you should see your files listed). If you're on the main S3 page showing all buckets, click your bucket name first to open it.
Need help? I can't find the Upload button in my S3 bucket.
Invalidate the CloudFront cache and verify your update
Your S3 bucket now has the updated file. But CloudFront has already cached the old version at its edge locations around the world. Without telling CloudFront to fetch fresh content, visitors will keep seeing the old heading.
What is cache invalidation?
A CDN like CloudFront stores copies of your files at edge locations so they load fast for visitors. Cache invalidation tells CloudFront to throw away those stored copies and pull fresh versions from S3 the next time someone requests them.
- Navigate to your CloudFront distribution in the AWS Console.
- Click the Invalidations tab.
- Click Create invalidation.
- In the object path field, type /* to clear all cached files.
- Click Create invalidation.
- Wait for the status to change to Completed. This usually takes under 1 minute.
What does /* do?
The /* wildcard path tells CloudFront to invalidate every cached object in the distribution. You could also target a single file like /index.html, but the wildcard is useful when you're unsure which files changed.
Before you refresh your CloudFront URL, think about what you expect to see. Will the page show the old heading or the new one?
- Open a new browser tab and paste your CloudFront distribution domain name (the .cloudfront.net URL from earlier).
- Press Enter to load the page.
You should see your new heading ("Welcome to my AWS-hosted site!") displayed on the live page. The invalidation cleared the cached version, and CloudFront pulled the fresh file from S3.
Still seeing the old heading?
Try a hard refresh in your browser: Cmd+Shift+R (macOS) or Ctrl+Shift+R (Windows). Your browser itself caches pages separately from CloudFront.
If that doesn't work, go back to the Invalidations tab and confirm the status shows Completed. If it still shows "In Progress", wait another minute and try again.
Still stuck? My CloudFront site still shows old content after invalidation.
Secret mission
Add a Custom 404 Error Page
Right now, if someone visits a path that doesn't exist on your site, they see an ugly XML error from S3. In this secret mission, you'll create a custom 404 page and configure CloudFront to serve it automatically. Your site will feel polished and professional, even when someone lands on a broken link.
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs entirely within the AWS Free Tier, so there are no ongoing costs while your site stays live.
Resources you used:
- 1 S3 bucket (storing index.html, style.css, and 404.html).
- 1 CloudFront distribution (serving your site over HTTPS globally).
- Local my-aws-website folder on your Desktop.
Keep everything running
No action needed. Choose this if you want your site to stay live and accessible.
- Your site stays live at no cost under the CloudFront Flat-Rate Free plan ($0/month for CDN) and 5GB of S3 storage included monthly.
- Your local my-aws-website folder remains on your Desktop for future edits.
Pause - I'll come back to this later
There is no true "pause" for static hosting. If you want to stop serving the site temporarily, you can disable the CloudFront distribution. Your S3 files remain stored at negligible cost.
- In the AWS Console, navigate to CloudFront and select your distribution.
- Open the distribution's General settings and click Disable.
- Wait for the status to show as Disabled (this takes a few minutes).
- Your S3 bucket and files remain intact. When you want to resume, re-enable the distribution from the same settings page.
Delete - I don't want to use this again
Remove all project resources completely. This stops any potential charges and cleans up your AWS account.
- In the AWS Console, navigate to CloudFront and select your distribution.
- Open the distribution's General settings and click Disable. Wait for the status to change to Disabled (this takes a few minutes).
- Once disabled, select the distribution and click Delete.
- Navigate to S3 in the AWS Console. Select your bucket and click Empty. Confirm the action by typing the required confirmation text.
- After the bucket is emptied, select it again and click Delete. Confirm by typing the bucket name.
- Delete the local my-aws-website folder from your Desktop by dragging it to the Trash (macOS) or right-clicking and selecting Delete (Windows).
That's a Wrap!
That's a Wrap!
Nice work! You just hosted a live static website on AWS using S3 for secure storage and CloudFront for global delivery over HTTPS.
You've learned how to:
- Host a static website on AWS using a private S3 bucket secured with Origin Access Control, the modern AWS best practice for keeping files private.
- Serve your site globally over HTTPS using a CloudFront distribution without configuring any certificates manually.
- Run the full deployment cycle: edit locally, upload to S3, invalidate the CloudFront cache, and verify changes live.
- Secret Mission: Configure a custom 404 error page by intercepting CloudFront error responses so visitors see a friendly message instead of a generic XML error.
Ready to quiz yourself?