How to Host a Static Website on Amazon S3

Why Amazon S3 is Ideal for Static Websites?

Think of Amazon S3 as a global, infinitely scalable filing cabinet in the cloud. You upload your website files (HTML, CSS, JavaScript, images) into it, and S3 handles everything else: storing them, serving them to visitors, and scaling automatically if your site gets popular.

This is radically different from a traditional server you might rent (like a virtual machine). With a traditional server, you're responsible for the entire computer—the operating system, web server software (like Apache or Nginx), security patches, and scaling. You pay for that entire machine 24/7, even if no one visits your site.

The S3 Advantage

  • Cost Efficiency: You only pay for what you use. No monthly fee for an idle server. You pay a tiny fraction of a cent per gigabyte stored and per thousand file requests.
  • Automatic Scaling: Whether 10 people or 10 million visit, S3 handles the traffic without you lifting a finger. There's no configuration.
  • High Availability: Amazon guarantees your files will be accessible across multiple data centers.

Visualizing the Transformation

Many beginners think S3 is just for storage. But with one click, it becomes a web server. Try the toggle below to see the transformation.

my-website-bucket

AWS S3

Static Website Hosting

Status: Disabled (Storage Only)

Common Misconception: S3 only stores data, not host websites

This belief comes from S3's original identity as "Simple Storage Service." People think "storage" means a hard drive you have to manually mount somewhere else to be useful.

Why that belief is inaccurate: S3 has a built-in, dedicated feature called "Static Website Hosting." When you enable it on a bucket, S3 transforms from a simple storage unit into a web server endpoint. It automatically:

  1. Assigns your bucket a unique web address (like your-bucket-name.s3-website-region.amazonaws.com).
  2. Serves your index.html file as the default page when someone visits the root address.
  3. Returns proper HTTP error codes (like 404) for missing files.
  4. Delivers your files over HTTPS (with a free Amazon certificate).

So, you're not just storing files; you're instructing S3 to act as a web server specifically for static content. The service that was "just storage" now directly handles the HTTP requests a website needs. This is why it's the simplest, most cost-effective way to host a static site.

How to Host a Static Website on S3

Let's demystify the process with a simple analogy. Imagine your S3 bucket as a single, physical shelf in a vast warehouse. You place your website's files—index.html, about.html, css/style.css—onto this shelf, each with its exact spot.

The "Shelf Address" is your bucket name (e.g., my-awesome-site), and the "File Position" is its key (like css/style.css).

The Warehouse Analogy in Action

By default, S3 warehouses are private. No one can see the shelves unless you explicitly unlock them. Toggle the switch below to enable "Static Website Hosting" and see how the warehouse opens its doors.

Bucket: my-portfolio
LOCKED
index.html
style.css
Private Storage
Static Website Hosting

Status: Disabled (Storage Only)

Common Pitfall: The "403 Forbidden" Error

Here is a scenario every beginner encounters. You upload your index.html to the bucket. You copy the URL. You paste it into your browser.

Result: 403 Forbidden or Access Denied.

Why did this happen?

Uploading files does not make them public. By default, an S3 bucket is a private vault. Even if you upload a file, S3's default rule is "No one can see this."

When you enable "Static Website Hosting", two critical things happen behind the scenes that fix this:

  1. A Public Endpoint is created: S3 generates a specific URL designed for web traffic (e.g., http://bucket-name.s3-website-region.amazonaws.com).
  2. A Bucket Policy is applied: S3 automatically attaches a rule that says, "Allow everyone ("*") to perform the s3:GetObject action on these files."

Without this second step—the policy change—S3 acts like a strict librarian who refuses to hand over books to strangers. The "Static Website Hosting" switch is the key that unlocks the shelf and invites the public in.

Amazon S3 Static Hosting Tutorial

Welcome to the construction site! Deploying your website to S3 is exactly like building a house. You can't paint the walls before you've laid the foundation, and you can't live there without electricity.

If you skip a step, the house is unusable. Let's walk through the four essential phases of construction, in the exact order you must perform them.

The 4-Step Construction Plan

Click "Next Step" to build your S3 website. Notice how each phase depends on the previous one.

Empty Plot of Land

1

Lay the Foundation

Create a unique Bucket Name.

2

Build the Structure

Upload all files (HTML, CSS, Images).

3

Install Utilities

Enable "Static Website Hosting" settings.

4

Connect to the Grid

Set public permissions (Bucket Policy).

Common Pitfall: The "Missing Room" Syndrome

Here is a very common mistake beginners make. You upload your index.html file, and you think you are done. You open the URL, and it loads... mostly.

Why this is like building a house with only a front door:

The "Only index.html" Myth

Uploading just the index.html is dangerous. Your website is a collection of files, not a single file.

If your HTML file contains a link to /about.html or references css/style.css, and those files are not in the bucket, the browser will return a 404 Not Found error for every single link and style sheet.

Broken Assets

Without your CSS and JS files, your site will look like a plain text document from 1995.

Broken Navigation

If you click a menu link to "Contact Us" and contact.html isn't uploaded, the user hits a dead end.

The Golden Rule: Your S3 bucket must be an exact mirror of your local project folder. If you have a folder structure like images/logo.png on your computer, that exact path must exist in the cloud. S3 does not guess; it only serves what you give it.

AWS S3 Beginner Guide: From Private Locker to Public Website

Let's build the intuition. Imagine Amazon S3 as a massive, secure cloud storage locker. You rent a locker (called a bucket) and place your website files—HTML, images, CSS—inside it.

By default, this locker is locked and private. Only you hold the key. To let visitors view your site, you must explicitly hand out copies of that key by adjusting permissions. S3's "Static Website Hosting" feature is simply the mechanism that transforms your private locker into a public display case with its own web address.

Visualizing the Permission Model

Most beginners think uploading a file makes it public. It doesn't. Watch how the "Bucket Policy" acts as the master key.

Bucket: my-website-files
LOCKED
index.html
Private Storage (Access Denied)
Bucket Policy

Principal: "Specific User" (Private)

Common Mistake: The "403 Forbidden" Error

When you create an S3 bucket, AWS configures it as private by default. This is a security safeguard—your files are shielded from the public unless you grant access.

A frequent error is assuming that uploading files or enabling "Static Website Hosting" automatically makes them publicly readable. It does not. Without explicit public read permissions, visitors accessing your site's URL will encounter a 403 Forbidden error.

Why does this happen?

By default, an S3 bucket is a private vault. Even if you upload a file, S3's default rule is "No one can see this." You must explicitly change the Bucket Policy to open the door.

The Fix: Configuring the Bucket Policy

The fix is to attach a bucket policy that allows public s3:GetObject actions. This policy is typically added automatically when you enable static hosting via the AWS console, but if you create or modify the bucket programmatically, you must add it manually.

bucket-policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": ""*"",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::YOUR-BUCKET-NAME/*"
    }
  ]
}
Principal: "*"

This is the "Who". The asterisk * means anyone on the internet.

Action: GetObject

This is the "What". It permits downloading objects (your website files).

Resource: /*

This is the "Where". The /* applies the rule to all files in the bucket.

Replace YOUR-BUCKET-NAME with your actual bucket name. Without this policy, S3 blocks all public requests—even if static hosting is enabled—because the bucket remains locked. The default privacy is intentional; your job is to deliberately open the door for your website's visitors.

Configure S3 Bucket for Website

Let's use a powerful analogy to understand configuration. Think of your S3 bucket as a physical mailbox at a public post office. You want anyone to be able to walk up, open the little door, and retrieve a letter (your index.html file).

To make this work, you must do two specific things:

1. The Sign (Static Hosting)

You must put a sign on the mailbox that says "Public Pickup Area". In AWS terms, this is enabling "Static Website Hosting" and setting the index.html document.

2. The Unlocked Door (Bucket Policy)

You must leave the mailbox unlocked for a specific action—only letting people take letters out. This is the Bucket Policy that allows public s3:GetObject.

If you only put up the sign but leave the mailbox locked, people will see your sign, try to open it, find it locked, and walk away frustrated—that's your 403 Forbidden error. The configuration isn't complete until both the sign and the unlocked state are in place.

The Configuration Mailbox

Configure the mailbox below to make it a valid website. Note that you need both the sign and the unlocked door.

Bucket: my-website-files
Offline
Locked
Static Hosting
Bucket Policy

Required Settings for Website Hosting

To transform your bucket into a functional website, you must explicitly configure these three settings. Missing any one of them will result in an error.

  1. Enable Static Website Hosting:
    In the bucket's Properties tab, find the "Static website hosting" block and enable it. This creates the public website endpoint (e.g., http://your-bucket.s3-website-region.amazonaws.com) and tells S3 to act as a web server for this bucket.
  2. Specify Index and Error Documents:
    Within that same setting, you must enter the exact key names of your files:
    • Index document: Typically index.html. This is the default file served when someone visits your bucket's root URL.
    • Error document (optional but recommended): e.g., 404.html. This custom page is served when a requested file doesn't exist.
  3. Apply a Public Read Bucket Policy:
    This is the critical "unlock" step. The bucket must have a policy that grants the s3:GetObject permission to the public ("Principal": "*"). When you enable static hosting through the AWS Management Console, AWS often suggests and can apply a basic policy for you. If you skip this or remove it, your site will return 403 Forbidden.

The Sequence Matters

Ideally, upload your files first, then enable static hosting (which sets the index/error docs), and finally verify the public permission policy exists. All three are required and interdependent.

Pitfall: Incorrect Bucket Policy Leads to 403 Errors

Even with static hosting enabled, a misconfigured bucket policy will lock your site down. The most common errors are:

Missing /* in Resource

The policy must apply to all objects in the bucket.

// WRONG: Only allows access to the bucket itself, not its contents
"Resource": "arn:aws:s3:::YOUR-BUCKET-NAME"
Wrong Action

The action must be s3:GetObject (read/download). s3:ListBucket alone is insufficient for direct file access via the website endpoint.

How to Correct the Policy

If you encounter a 403 error, follow these steps to verify your policy:

  1. Go to your bucket's Permissions tab.
  2. Under Bucket policy, click Edit.
  3. Ensure your policy JSON matches this exact structure, replacing YOUR-BUCKET-NAME:
bucket-policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": ""*"",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::YOUR-BUCKET-NAME/*"
    }
  ]
}
  1. Click Save changes.

Why this works: This policy is the formal "key" you give to the world. It states: "Anyone (Principal: "*") is allowed to perform the s3:GetObject action (download files) on any object (Resource: .../*) inside this specific bucket." S3's static website endpoint checks this policy on every request for a file. If the policy allows the GetObject action for the public, S3 serves the file. If the policy is absent or denies access, S3 returns 403.

Test your fix: After saving the policy, wait 10-20 seconds (for AWS propagation), then access your bucket's website endpoint URL. You should now see your index.html page. If you still see 403, double-check the bucket name in the Resource ARN matches your bucket exactly (case-sensitive) and that the /* is present.

Cloud Static Site Deployment

Think of deployment as the final act of turning your private cloud shelf into a public display case. You've already uploaded all your website files—the shelf is stocked. But until you enable static website hosting and set the index document, that shelf remains locked in a private warehouse. No one can access it.

Deployment is the moment you instruct S3: "Serve these files to anyone who asks for them, and start with index.html when they visit the root address."

Visualizing the "Flip"

Uploading files is just storage. Deployment is the configuration switch that opens the doors. Toggle the switch below to see the transformation from a private bucket to a live website.

Bucket: my-portfolio-site
Private
index.html
Storage Only
Static Website Hosting

Status: Disabled (Storage Only)

Steps to Make the Site Live

After you have uploaded every file from your website project to the S3 bucket, follow these precise steps to activate the site.

  1. In the AWS Console: Open your bucket's Properties tab.
  2. Locate the Setting: Scroll to the Static website hosting section and click Edit.
  3. Enable It: Select Enable.
  4. Set the Index: In the Index document field, type index.html (or your main entry file's name).
  5. Set the Error Page (Recommended): In the Error document field, type 404.html.
  6. Save: Click Save changes.

S3 now assigns your bucket a unique website endpoint (e.g., http://your-bucket.s3-website-us-east-1.amazonaws.com). Visiting that URL will serve your index.html file.

Critical Reminder: The 403 Forbidden Error

If you see a 403 Forbidden error after these steps, your bucket lacks public read permissions. The static hosting setup usually adds a basic bucket policy automatically, but if it didn't (e.g., if configured via CLI), you must manually attach a policy granting s3:GetObject to Principal: "*". The prior section on bucket policies covers this fix in detail.

Common Misconception: Uploading files instantly makes the site live

Uploading files to an S3 bucket does not publish a website. By default, an S3 bucket is private. All objects inside it are inaccessible to the public internet.

The "Static website hosting" setting is the separate switch that:

1. Creates Endpoint

Generates the public HTTP URL (e.g., s3-website...).

2. Sets Default Page

Configures S3 to serve index.html as the default page.

3. Grants Access

Typically attaches a bucket policy allowing public reads.

Without enabling this setting, your bucket has no public URL, and S3 will not act as a web server. The files exist but are locked away. Therefore, deployment is not file transfer—it is configuration. You must explicitly enable static website hosting and specify the index document to make your uploaded files publicly reachable.

Advanced Settings: Custom Domains, SSL, and CloudFront

Your S3 bucket's default website endpoint (e.g., your-bucket.s3-website-us-east-1.amazonaws.com) is like a temporary P.O. box. It works, but it's impersonal and hard to remember. To build a professional brand, you need a permanent street address—your custom domain (e.g., www.yourbrand.com).

However, there's a catch: S3 static website endpoints do not support HTTPS directly for custom domains. Modern browsers require that security lock (SSL/TLS) to show the "Secure" padlock icon.

The Solution: The CloudFront Middleman

To fix this, we place a CloudFront Distribution in front of your bucket. CloudFront acts as a secure gateway:

  • It accepts secure HTTPS connections from users.
  • It fetches the content from your S3 bucket.
  • It delivers it back to the user securely.

Visualizing the DNS Connection

Think of your Domain as a street sign and CloudFront as the building. Right now, the sign points to "Nowhere." Toggle the switch to connect the sign to the building (CloudFront).

DNS Zone: example.com
Disconnected
Visitor
Domain
www.example.com
CloudFront
d1234abcd.cloudfront.net
S3 Bucket
my-site-files
DNS Not Configured
Update DNS Record (CNAME)

Status: Not Pointing

Common Pitfall: Forgetting to Update DNS Records

This is the most common reason for "downtime" during deployment. After creating your CloudFront distribution and getting your SSL certificate, you might think you are done.

But wait! If you don't update your DNS records, your domain (e.g., www.yourdomain.com) still points to your old host—or nowhere at all.

Why does this happen?

CloudFront gives you a new address (e.g., d1234abcd.cloudfront.net). You must explicitly tell your domain registrar to send traffic there. Without this step, the "Street Sign" points to an empty lot.

How to avoid downtime:

  1. Wait for Deployment: Ensure your CloudFront distribution status is "Deployed" (not "In Progress") before changing DNS.
  2. Update DNS Immediately: In your domain registrar (like GoDaddy, Namecheap, or Route 53), create a CNAME record for www pointing to your CloudFront domain.
  3. Use Low TTL: If possible, lower your DNS TTL (Time To Live) to 300 seconds 24 hours before the switch. This makes the change propagate faster.
DNS Record Example
# This tells the world where to find your site
www.yourdomain.com.  300  IN  CNAME  d1234abcd.cloudfront.net.
The "CNAME"

This is the "Alias" type. It says: "If someone asks for 'www', send them to 'CloudFront' instead."

The "300"

This is the TTL (Time To Live) in seconds. A lower number means the change happens faster across the internet.

The CloudFront Domain

This is the unique address AWS gave you. You must copy-paste this exactly.

Pro Tip: Root domains (like yourdomain.com without 'www') cannot use CNAME records. Instead, use an ALIAS or ANAME record (if supported by your registrar) or a redirect service. But for www, CNAME is the standard and correct choice.

Setting Up Bucket Policy for Public Access

Let's use a powerful analogy to understand this. Think of your S3 bucket as a private warehouse. By default, the warehouse is locked tight.

Even if you enable "Static Website Hosting" (which puts up a sign saying "Public Library"), the Security Guard (S3) still needs explicit written orders. Without a policy, the guard follows the default rule: "No one enters."

The Bucket Policy is that written order. It tells the guard: "Allow any visitor to enter and read any document, but do not let them rearrange or remove anything."

The Security Guard Simulator

Toggle the policy below to see how the Security Guard reacts. Notice the difference between a dangerous policy and a safe one.

Bucket: my-website-files
LOCKED
index.html
Private Storage
Policy Type

Status: Private (No Policy)

The Policy in Plain English

You don't need to be a JSON expert to understand a bucket policy. It simply answers three questions:

1. Who?

"Principal": "*"
This means anyone on the internet.

2. What Action?

"Action": "s3:GetObject"
This means download/read only.

3. To What?

"Resource": ".../*"
This means all files inside the bucket.

Common Pitfall: The "Open Writable" Bucket

A very common mistake beginners make is using a wildcard action like "s3:*" just to "fix" a 403 error quickly.

Why is "s3:*" dangerous?

This grants the entire internet the power to upload, delete, and modify your files. A hacker could replace your website with a phishing page or delete your assets entirely.

The Golden Rule: Your website bucket should be a read-only display case, not a public dropbox. The safest policy grants s3:GetObject permission only.

bucket-policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": ""*"",
      "Action": ""s3:GetObject"",
      "Resource": "arn:aws:s3:::YOUR-BUCKET-NAME/*"
    }
  ]
}

Replace YOUR-BUCKET-NAME with your actual bucket name. This policy is the "key" you give to the world to let them see your site, but nothing else.

Frequently Asked Questions (FAQ)

Welcome to the troubleshooting workshop! Even the best architects hit a snag now and then. Here are the most common questions I hear from students, answered with the "why" and the "how."

Why does my website show a 403 error after uploading files?

Think of your S3 bucket as a locked display case. Uploading files is like placing items inside, but the case remains locked by default. The 403 error means the public guard (S3) is blocking access because the bucket policy—the written permission slip—is missing or incorrect.

The Fix: The Permission Slip

Even if you enabled "Static Website Hosting," AWS sometimes fails to attach the policy automatically (especially via CLI). You must explicitly grant permission.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": ""*"",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::YOUR-BUCKET-NAME/*"
    }
  ]
}

⚠️ Crucial: The /* at the end is vital. Without it, you are giving permission to the bucket itself, but not the files inside it.

Why can't I see my index.html when visiting the site URL?

You've uploaded index.html, but S3 doesn't know it's the default page. This is like putting a book on a shelf but not labeling it "Start Here."

The Technical Reason: The Index document setting in "Static website hosting" must exactly match your homepage's filename (usually index.html). If it's blank, misspelled, or set to home.html while your file is named index.html, S3 will fail to load it automatically.

Professor Pixel's Tip:

Always ensure index.html is in the root of your bucket, not inside a folder like docs/index.html, unless you specifically set the index setting to that path.

When should I use S3 for static sites versus a traditional web server?

Use S3 If...

  • Your site is static (HTML/CSS/JS only).
  • You want zero maintenance (no OS updates).
  • You expect variable traffic (S3 scales automatically).
  • You prioritize simplicity and cost.

Use a Server (EC2) If...

  • You need dynamic functionality (PHP, Python, databases).
  • You require custom server configs (specific Apache modules).
  • You want full control over the OS.

What are the limits of S3 static website hosting?

S3 is powerful, but it is intentionally simple. Here is what it cannot do out of the box:

Limit Why It Exists Workaround
No HTTPS on custom domains S3 endpoints only support HTTP. Use CloudFront (CDN) in front.
No server-side processing S3 only serves files; it doesn't execute code. Use AWS Lambda + API Gateway.
No directory listings Security by default. Create a custom index.html in folders.

How do I troubleshoot missing files after deployment?

If your site loads but styles/images are broken (404s), follow this checklist. Click the items to verify them.

Verify upload completion (Check S3 Console)
Check index/error document settings in Properties
Confirm public permissions (Bucket Policy)
Test the website endpoint URL directly
Clear browser cache (or use Incognito mode)

Can I use S3 for dynamic websites?

Short Answer: No.

S3 is a static file store. It cannot:

  • Execute server-side code (PHP, Ruby, Python, Node.js).
  • Connect to databases (MySQL, PostgreSQL).
  • Process form submissions (without a separate backend service).

The Modern Alternative: Use the Jamstack approach. Keep S3 for static assets, but use AWS Lambda + API Gateway for backend logic (like form handling).

What costs should I expect for a small static site?

For a typical small site (e.g., 50 MB storage, 10k monthly visitors, 100k requests), the cost is remarkably low.

Storage
$0.00
~50MB is free
Requests
$0.04
100k requests
Total
< $1.00
per month

*Estimates based on us-east-1 pricing. Costs scale linearly with traffic.

Is it possible to automate S3 static site deployment?

Yes! Manual uploads are fine for testing, but automation ensures consistency. The most popular tool for this is the AWS CLI sync command.

# Sync local 'build' folder to S3 bucket (deletes removed files)

aws s3 sync ./build/ s3://YOUR-BUCKET-NAME/ --delete

You can integrate this command into a package.json script or a GitHub Action to deploy your site automatically every time you push code.

Post a Comment

Previous Post Next Post