Host a Static Website on AWS
Host a portfolio site on S3 and serve it globally via CloudFront with HTTPS.
Introduction
30 Second Summary
Every website needs a home. When you type a URL into your browser, a server somewhere delivers the files that make up that page.
In this project, you will host a personal portfolio website on AWS infrastructure. You'll store your site in an S3 bucket, deliver it globally through a CloudFront CDN, and secure it with HTTPS.
What You'll Build
A personal portfolio landing page stored in S3 and served globally via CloudFront with Origin Access Control (OAC) so only CloudFront can read from your bucket.
By the end of this project, you'll have:
- An AWS Free Tier account with $100+ in credits to explore cloud services.
- An S3 bucket configured to store your website files as objects.
- A CloudFront distribution delivering your site over HTTPS with Origin Access Control.
- Secret Mission: Add a custom error page and configure cache invalidation to instantly push updates to your live site.
Are there any prerequisites?
None. This guide walks you through everything from creating your AWS account to deploying your site. All you need is a web browser and a text editor (Notepad works perfectly).
Set Up Your AWS Account
Before you can host anything on AWS, you need an account. The AWS Free Tier gives new customers $100 in credits immediately on sign-up, with the ability to earn up to $100 more by completing activities.
In this step, you will create your free AWS account, choose a region for your resources, and confirm that the S3 service is accessible.
In this step, get ready to:
- Create an AWS Free Tier account with $100 in credits.
- Select a region close to your location.
- Verify your account is active and S3 is accessible.
Create your AWS Free Tier account
- In your browser, navigate to the AWS Free Tier page:
- https://aws.amazon.com/free/
- Click Create a free account.
- Enter your email address, choose an account name, and verify your email with the code AWS sends you.
- Create a root user password and click Continue.
- Select Personal as the account type and fill in your contact information.
- Accept the AWS Customer Agreement and click Continue.
Why do I need a credit card?
AWS requires a valid payment method to verify your identity and prevent abuse. You will not be charged unless you upgrade to a Paid plan. The Free plan expires after 6 months or when your credits run out, whichever comes first.
- On the Billing information page, enter your credit or debit card details and click Verify and Add.
- Verify your phone number by entering the PIN that AWS sends you, then click Continue.
- On the Select a support plan page, keep the default Basic Support (free of charge) and click Complete sign up.
- When prompted to choose your account plan, select the Free account plan. This gives you $100 in credits immediately with no charges unless you upgrade.
What is the Free account plan?
The Free account plan lets you explore AWS services for up to 6 months with no charges. You receive $100 in credits on sign-up and can earn up to $100 more by completing activities. Your account closes automatically after 6 months or when credits run out.
The Paid plan gives full access to all services but charges you for usage beyond your credits. For this project, the Free plan is all you need.
- After sign-up completes, sign in to the AWS Management Console using the email and password you just created.
Account not activating?
Account activation usually takes a few minutes but can take up to 24 hours. If you see a Complete Sign Up button, you can ignore it. AWS sends a confirmation email when activation is complete. Check your spam folder if you do not see it.
help me troubleshoot my AWS account activation
Navigate the AWS Console and select a region
The AWS Management Console is your main dashboard for managing all AWS services. Every resource you create lives in a specific region, which is a physical location where AWS has data centers.
- Once signed in, look at the top-right corner of the console. You will see a region name displayed (e.g., US East (N. Virginia)).
- Click the region name to open the region selector dropdown.
- Select a region geographically close to you. If you are unsure, choose US East (N. Virginia) us-east-1.
Why does region matter?
Your region choice affects where your data is physically stored and the latency (speed) between you and your resources. Picking a region close to you means faster uploads and downloads when you manage your S3 bucket.
Verify your account is active
A quick check will confirm your account is ready and you can access the S3 service you will use in the next step.
- Click the search bar at the top of the AWS Console.
- Type S3 and press Enter.
- You should see the Amazon S3 console load with a General purpose buckets section in the left navigation pane. This confirms your account is active.
Seeing a permissions error?
If you see an error about permissions or the page does not load, your account may still be activating. Wait a few minutes and refresh the page. New accounts occasionally take up to 24 hours to fully activate, though most are ready within minutes.
help me fix AWS console permission errors on a new account
Your AWS account is live and ready to go. Next up, you will create an S3 bucket and upload your website files to it.
Create an S3 Bucket and Upload Your Website
Your AWS account is active and your region is set. Now it's time to create storage for your website files.
In this step, you'll create an S3 bucket (a container for your files in the cloud) and upload a portfolio website made of HTML and CSS.
In this step, get ready to:
- Create an S3 bucket with a globally unique name.
- Write your website's HTML and CSS files.
- Upload your files to the bucket.
Create a new S3 bucket
An S3 bucket is where AWS stores your files (called objects). Every bucket needs a name that is unique across all of AWS.
- Return to the S3 console by searching S3 in the top search bar and selecting it.
- In the left navigation pane, choose General purpose buckets.
- Click Create bucket.
- In the Bucket name field, enter a unique name. It must be all lowercase with no spaces (e.g., my-portfolio-site-2026).
- Leave all other settings at their defaults.
- Scroll down and click Create bucket.
You should see your new bucket listed on the General purpose buckets page.
What is an S3 bucket?
Think of an S3 bucket like a folder in the cloud that holds your files. Unlike folders on your computer, S3 buckets have globally unique names because anyone on AWS could create one.
The default settings keep your bucket private. Nobody can access your files unless you explicitly grant permission. This is the secure starting point for every AWS project.
Seeing a bucket name error?
S3 bucket names must be globally unique across all AWS accounts. If you see an error, try adding random numbers or your name to make it unique (e.g., my-portfolio-jsmith-2026).
Make sure your name uses only lowercase letters, numbers, and hyphens. No spaces or uppercase letters.
Help me fix my S3 bucket name error.
Create and upload your website files
Your website needs two files. An HTML file provides the page structure. A CSS file provides the visual styling.
- Press the Windows key (Windows) or Cmd+Space (macOS) to open your search bar.
- Type Notepad and press Enter to open it.
- Paste the following HTML content into the blank Notepad window:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Portfolio - Cloud Engineer</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<h1>Hi, I'm a Cloud Engineer</h1>
<p>Building on AWS, one project at a time.</p>
</header>
<main>
<section>
<h2>About This Site</h2>
<p>This website is hosted on Amazon S3 and delivered globally via Amazon CloudFront. It uses Origin Access Control (OAC) so that only CloudFront can read from the S3 bucket.</p>
</section>
<section>
<h2>Architecture</h2>
<ul>
<li>Storage: Amazon S3 (object storage)</li>
<li>CDN: Amazon CloudFront (global edge locations)</li>
<li>Security: Origin Access Control (OAC) + HTTPS</li>
</ul>
</section>
<section>
<h2>Skills Demonstrated</h2>
<ul>
<li>S3 bucket creation and configuration</li>
<li>CloudFront distribution setup</li>
<li>IAM bucket policies</li>
<li>Content delivery and caching</li>
</ul>
</section>
</main>
<footer>
<p>Hosted on AWS | Built as part of my cloud engineering journey</p>
</footer>
</body>
</html>
What does this HTML do?
This is a simple portfolio page with three sections. The header introduces you as a cloud engineer. The main content describes the site architecture, the AWS services involved, and the skills you're demonstrating.
The <link rel="stylesheet" href="style.css"> line connects this HTML to the CSS file you'll create next.
- Click File, then Save As.
- Navigate to your Desktop.
- Change the Save as type dropdown from Text Documents (*.txt) to All Files (*.*).
- In the File name field, type index.html and click Save.
File saved as index.html.txt?
If you forgot to change Save as type to All Files, Notepad adds .txt to the end of your filename. Delete the file and save again with the correct setting.
Help me save my HTML file correctly in Notepad.
Now create the second file for styling.
- In Notepad, click File, then New to open a blank document.
- Paste the following CSS content:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
line-height: 1.6;
color: #333;
max-width: 800px;
margin: 0 auto;
padding: 2rem;
background-color: #f5f5f5;
}
header {
text-align: center;
padding: 3rem 0;
border-bottom: 2px solid #232f3e;
margin-bottom: 2rem;
}
header h1 {
font-size: 2.2rem;
color: #232f3e;
margin-bottom: 0.5rem;
}
header p {
font-size: 1.2rem;
color: #666;
}
section {
background: white;
padding: 1.5rem;
margin-bottom: 1.5rem;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
section h2 {
color: #232f3e;
margin-bottom: 1rem;
font-size: 1.4rem;
}
ul {
list-style-position: inside;
padding-left: 0.5rem;
}
ul li {
margin-bottom: 0.5rem;
}
footer {
text-align: center;
padding: 2rem 0;
color: #666;
border-top: 1px solid #ddd;
margin-top: 2rem;
}
What does this CSS do?
This stylesheet gives your portfolio a clean, professional look. It centers the content, adds card-style shadows to each section, and uses AWS's brand color (#232f3e) for headings and borders.
The * selector at the top resets browser default spacing so your layout looks consistent across different browsers.
- Click File, then Save As.
- Navigate to your Desktop (the same location where you saved index.html).
- Change the Save as type dropdown to All Files (*.*).
- In the File name field, type style.css and click Save.
You should now see both index.html and style.css on your Desktop.
✔️ Awesome, I've got everything!
Great. Make sure both files are on your Desktop before moving on to the upload.
ⓧ I'd like to double check the full code
Here is the complete index.html file:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Portfolio - Cloud Engineer</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<h1>Hi, I'm a Cloud Engineer</h1>
<p>Building on AWS, one project at a time.</p>
</header>
<main>
<section>
<h2>About This Site</h2>
<p>This website is hosted on Amazon S3 and delivered globally via Amazon CloudFront. It uses Origin Access Control (OAC) so that only CloudFront can read from the S3 bucket.</p>
</section>
<section>
<h2>Architecture</h2>
<ul>
<li>Storage: Amazon S3 (object storage)</li>
<li>CDN: Amazon CloudFront (global edge locations)</li>
<li>Security: Origin Access Control (OAC) + HTTPS</li>
</ul>
</section>
<section>
<h2>Skills Demonstrated</h2>
<ul>
<li>S3 bucket creation and configuration</li>
<li>CloudFront distribution setup</li>
<li>IAM bucket policies</li>
<li>Content delivery and caching</li>
</ul>
</section>
</main>
<footer>
<p>Hosted on AWS | Built as part of my cloud engineering journey</p>
</footer>
</body>
</html>
And here is the complete style.css file:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
line-height: 1.6;
color: #333;
max-width: 800px;
margin: 0 auto;
padding: 2rem;
background-color: #f5f5f5;
}
header {
text-align: center;
padding: 3rem 0;
border-bottom: 2px solid #232f3e;
margin-bottom: 2rem;
}
header h1 {
font-size: 2.2rem;
color: #232f3e;
margin-bottom: 0.5rem;
}
header p {
font-size: 1.2rem;
color: #666;
}
section {
background: white;
padding: 1.5rem;
margin-bottom: 1.5rem;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
section h2 {
color: #232f3e;
margin-bottom: 1rem;
font-size: 1.4rem;
}
ul {
list-style-position: inside;
padding-left: 0.5rem;
}
ul li {
margin-bottom: 0.5rem;
}
footer {
text-align: center;
padding: 2rem 0;
color: #666;
border-top: 1px solid #ddd;
margin-top: 2rem;
}
Now upload both files to your S3 bucket.
- Switch back to the AWS Console in your browser.
- Click your bucket name to open it.
- Click Upload.
- Click Add files.
- Select both index.html and style.css from your Desktop.
- Click Upload.
You should see a green success banner at the top of the page confirming both files uploaded.
Upload failed or files not appearing?
Make sure you clicked your bucket name first to open it before clicking Upload. You need to be inside the bucket, not on the general buckets list.
If your files show as index.html.txt or style.css.txt, go back to Notepad and re-save them with Save as type set to All Files.
Help me upload my files to S3.
Verify your files are in the bucket
Your files are uploaded. Now confirm they're stored correctly and understand why you can't access them directly yet.
- Click Close on the upload success page to return to your bucket.
- Confirm you see both index.html and style.css listed in your bucket.
- Click on index.html to open its details.
- Find the Object URL field and copy the URL.
- Open a new browser tab and paste the Object URL into the address bar.
You should see an Access Denied error. This is expected and correct.
Why can't I access my file directly?
By default, S3 buckets block all public access. The Object URL points directly to your file in S3, but nobody has permission to read it yet.
This is a security best practice. In the next step, you'll set up CloudFront as the only service allowed to read from your bucket. Regular users will access your site through CloudFront, never directly from S3.
Your website files are safely stored in S3, but they're locked away from the public internet. Next up, you'll create a CloudFront distribution that serves these files to the world over HTTPS.
Create a CloudFront Distribution with OAC
Your S3 bucket is storing your website files securely, but nobody can access them yet. The bucket is private by default, which is exactly what we want.
In this step, you'll put CloudFront in front of your S3 bucket. CloudFront is AWS's CDN (content delivery network) that serves your files from edge locations around the world, giving visitors fast load times and automatic HTTPS encryption.
In this step, get ready to:
- Open the CloudFront console and start the distribution wizard.
- Connect your S3 bucket as the origin with recommended OAC settings.
- Wait for deployment and record your new CloudFront domain name.
Open CloudFront and create a distribution
A CloudFront distribution tells AWS how to deliver your content to users worldwide. You'll configure it to pull files from your S3 bucket and serve them over HTTPS.
- In the AWS Management Console, search for CloudFront in the top search bar.
- Select CloudFront from the search results to open the CloudFront console.
- Choose Create distribution.
- In the Distribution name field, enter My Portfolio Site.
- Select Single website or app, then choose Next.
- On the Domain setup page, skip the domain configuration for now.
- Choose Next to continue to the origin configuration page.
What is a CloudFront distribution?
A distribution is the main resource in CloudFront. It defines where your content lives (the origin), how it gets cached, and the domain name users visit to reach your site.
Think of it as the bridge between your private S3 bucket and the public internet. Users never touch your bucket directly.
Configure the S3 origin with recommended settings
The origin is where CloudFront fetches your files from. You'll point it at your S3 bucket and let AWS auto-configure Origin Access Control (OAC) for you.
- For Origin type, select Amazon S3.
- For S3 origin, choose Browse S3 and select the bucket you created earlier (e.g., my-portfolio-site-2026).
- For Settings, choose Use recommended origin settings.
- Choose Next.
What do recommended origin settings do?
When you choose recommended settings, CloudFront automatically sets up Origin Access Control (OAC). OAC means CloudFront sends signed requests to your S3 bucket, proving it is authorized to read your files.
It also configures caching, HTTPS redirect, and compression for you. This is the secure, production-ready approach recommended by AWS.
Why OAC instead of making the bucket public?
An older approach is to turn off Block Public Access on your S3 bucket and let anyone read it directly. That works, but it means anyone with the S3 URL can bypass CloudFront entirely.
OAC keeps your bucket fully private. Only your specific CloudFront distribution can read from it, giving you a tighter security posture and a single access point you control.
Create the distribution and record your domain name
- On the Enable security protections page, skip AWS WAF for now (you can enable it later).
- Choose Next.
- Choose Create distribution.
CloudFront automatically updates your S3 bucket policy to allow access via OAC. You do not need to write any policy manually.
- In the Details section of your new distribution, find the Domain name field.
- Record this domain name somewhere (it looks like d111111abcdef8.cloudfront.net). You will use this to access your live site.
- Wait for the Last modified field to change from Deploying to a date and time. This typically takes 3-5 minutes.
What is happening during deployment?
CloudFront is pushing your distribution configuration to edge locations around the world. Once complete, users anywhere can request your site and get served from the nearest edge location.
Distribution stuck on Deploying?
New distributions typically deploy within 3-5 minutes, but occasionally can take up to 15 minutes. Refresh the CloudFront console page to check for updates.
If it stays in Deploying for longer than 15 minutes, confirm you selected your S3 bucket correctly in the origin settings.
Help me troubleshoot my CloudFront distribution deployment
Your CloudFront distribution is live and connected to your S3 bucket through OAC. Next up, you'll visit your site through this new domain name and verify the full architecture is working end to end.
Test Your Live Site and Understand the Architecture
Your CloudFront distribution is deployed and connected to your S3 bucket. Now it's time to prove the architecture works.
In this step, you'll visit your live site, confirm that HTTPS is working, and verify that direct access to your S3 files is blocked. This test confirms that your OAC setup is correctly restricting access so only CloudFront can serve your content.
In this step, get ready to:
- Access your live website through your CloudFront domain over HTTPS.
- Verify that direct S3 access is blocked by OAC.
- Examine the bucket policy and understand the full request flow.
Access your website via CloudFront
Your CloudFront distribution has a unique domain name that serves your site globally over HTTPS. Let's visit it and confirm your portfolio page is live.
- Open a new browser tab.
- Return to the CloudFront console where you created your distribution.
- Copy the Distribution domain name (it looks like d111111abcdef8.cloudfront.net).
- In your browser's address bar, paste the domain name and add /index.html at the end, so it looks like https://d111111abcdef8.cloudfront.net/index.html.
- Press Enter.
Your portfolio page should load with the header "Hi, I'm a Cloud Engineer" and your three content sections styled with white cards on a grey background.
What does the HTTPS padlock mean?
Notice the padlock icon in your browser's address bar. CloudFront automatically provides a free SSL/TLS certificate for its .cloudfront.net domains. All traffic between your visitors and CloudFront's edge locations is encrypted.
If you had used S3's static website hosting feature instead, your site would only support HTTP (unencrypted). The CloudFront approach gives you HTTPS for free.
Seeing an Access Denied error on your CloudFront URL?
If you see an Access Denied XML error, your distribution may still be deploying. Wait 3-5 minutes and try again.
If the error persists after 5 minutes, return to the CloudFront console and check that the distribution status shows a timestamp (not "Deploying"). Also confirm you typed /index.html at the end of the URL.
Still stuck? Why is my CloudFront distribution returning Access Denied even after deployment?
Verify OAC is working by testing direct S3 access
Your site loads through CloudFront, but how do you know the bucket itself is actually private? Let's prove that nobody can bypass CloudFront and read your files directly from S3.
- Switch back to the S3 console tab in your browser.
- Click on your bucket name to open it.
- Click on index.html in the file list to open its details.
- Find the Object URL field in the Object overview section.
- Click the Object URL to open it in a new tab.
You should see an Access Denied error. This is the correct behavior.
Why is Access Denied a good thing here?
The Access Denied error proves that your bucket is private. Nobody on the internet can read your files by going directly to S3.
The only way to reach your content is through CloudFront, which uses Origin Access Control (OAC) to send signed requests to S3. S3 checks that the request came from your specific CloudFront distribution before returning the file.
Now let's look at the bucket policy that makes this access control work.
- Switch back to your S3 bucket (click the bucket name in the breadcrumb navigation at the top).
- Click the Permissions tab.
- Scroll down to the Bucket policy section and read the JSON policy.
What does this bucket policy do?
CloudFront automatically added this policy when you chose "Use recommended origin settings." The policy has three key parts:
- The Principal is set to cloudfront.amazonaws.com. This means only the CloudFront service can use this permission.
- The Action is s3:GetObject. CloudFront can read objects but cannot delete or modify them.
- The Condition scopes access to your specific distribution. Even other CloudFront distributions cannot read from this bucket.
Understand the architecture you built
You now have a working, secure static website on AWS. Let's walk through exactly what happens when someone visits your site.
- A user types your CloudFront URL into their browser. The request goes to the nearest CloudFront edge location (one of hundreds worldwide).
- CloudFront checks its local cache. If the file is already cached at that edge location, it returns the file immediately. No trip to S3 needed.
- On the first request (a cache miss), CloudFront fetches the file from your S3 bucket using OAC. This means CloudFront sends a signed request that proves its identity.
- S3 checks the bucket policy, confirms the request came from your specific CloudFront distribution, and returns the file.
- CloudFront caches the file at the edge for 24 hours (the default TTL). All subsequent requests from nearby users are served from cache.
Why does caching matter?
Caching means faster load times for your visitors. Instead of every request travelling all the way to your S3 bucket (which lives in one region), CloudFront serves files from whichever edge location is closest to the visitor.
A visitor in Tokyo gets your file from a Tokyo edge server. A visitor in London gets it from a London edge server. Both load in milliseconds instead of crossing the globe.
What is a CDN?
A CDN (Content Delivery Network) is a network of servers distributed around the world. CloudFront is AWS's CDN service. It copies your content to edge locations so users get fast responses no matter where they are.
Secret mission
Add a Custom Error Page and Cache Invalidation
Your site is live, but what happens when someone visits a page that does not exist? And what happens when you update your content but CloudFront keeps serving the old version from cache? In this mission, you will solve both problems by creating a custom error page and learning how to force CloudFront to clear its cache on demand.
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 uses AWS Free Tier credits, so there are no charges while your free plan is active (6 months or until credits are exhausted).
Cost warning
Your AWS Free plan expires after 6 months or when your credits run out. After that, leaving these resources running may incur small charges for S3 storage and CloudFront data transfer. If you are unsure whether you will return to this project, choose the Delete option below.
Resources you used:
- 1 S3 bucket (e.g., my-portfolio-site-2026) containing index.html, style.css, and 404.html.
- 1 CloudFront distribution ("My Portfolio Site") with Origin Access Control.
Keep everything running
No action needed. Choose this if you are still actively building or want your site to remain live at the CloudFront URL. Storage costs are minimal and fully covered by your Free Tier credits while your free plan is active.
- Your site stays live at your CloudFront URL.
- S3 storage for a few HTML and CSS files costs fractions of a cent per month.
- CloudFront provides 1 TB of free data transfer per month under the Free Tier.
Pause - I'll come back to this later
You cannot pause a CloudFront distribution in the traditional sense. However, with no traffic hitting your site, the effective cost is $0. Your site remains accessible but will not generate charges unless someone visits it at scale.
- If you want to stop serving traffic entirely, you can disable the CloudFront distribution without deleting it. This stops all requests but keeps the configuration intact.
- To disable: open the CloudFront console, select the checkbox next to your distribution, and choose Disable. Confirm by choosing Yes, Disable.
- To re-enable later, select the distribution and choose Enable.
Delete - I don't want to use this again
Remove all project resources to ensure $0 charges going forward. You must delete the CloudFront distribution first, then the S3 bucket.
Delete the CloudFront distribution
- Open the CloudFront console at https://console.aws.amazon.com/cloudfront/v4/home.
- Select the checkbox next to your distribution.
- Choose Disable, then choose Yes, Disable to confirm. Then choose Close.
- Wait until a new timestamp appears in the Last modified column. This may take a few minutes.
- Select the checkbox next to your distribution again.
- Choose Delete, then Delete again to confirm.
Empty and delete the S3 bucket
- Open the S3 console and choose General purpose buckets in the left navigation pane.
- Select the option next to your bucket name and choose Empty.
- Confirm by entering your bucket name in the text field, then choose Empty.
- After the bucket is empty, go back to the bucket list.
- Select the option next to your bucket name and choose Delete at the top of the page.
- Confirm by entering your bucket name, then choose Delete bucket.
Once both resources are deleted, your AWS account will incur no further charges from this project.
Nice Work!
Nice Work!
Nice work! You just built and deployed a live website on AWS infrastructure using S3, CloudFront, and IAM policies.
You've learned how to:
- Create an S3 bucket and upload a static website for object storage.
- Create a CloudFront distribution with Origin Access Control (OAC) for secure, globally-distributed content delivery over HTTPS.
- Understand an IAM bucket policy that restricts S3 access to your specific CloudFront distribution only.
- Secret Mission: Configured a custom error page and used cache invalidation to push live updates instantly to all edge locations.
Ready to quiz yourself?