Host a Static Website on S3

Learn how to host a static website on Amazon S3 with step-by-step instructions, troubleshooting tips, and what to study next in the AWS Cloud & DevOps with Python track.

Focus: host a static website on s3

Sponsored

You’ve built the HTML, CSS, and maybe a little JavaScript for your project, and now you need to get it online—fast, cheap, and without managing a server. Renting a VPS or spinning up EC2 just to serve a few static files is overkill, and it leaves you paying for idle CPU and patching operating systems you don’t care about. Amazon S3 can host your static website for pennies a month, scale to handle any traffic spike, and integrate perfectly with the Python and DevOps tooling you’re already learning—this lesson shows you exactly how to do it.

The problem this lesson solves

Every developer eventually needs to publish a static site: a personal portfolio, a documentation site, a landing page for a side project, or the frontend of a React app you’ll deploy later in this track. Traditional hosting puts you in charge of a server—you install nginx, configure SSL, monitor uptime, and pray the disk doesn’t fill up. That’s a heavy responsibility for files that never change.

S3 changes the equation. You upload your files to a bucket, turn on a single setting called static website hosting, and S3 serves them over HTTP at a public URL. There’s no server to patch, no load balancer to configure, and no minimum spend—you pay only for the storage you use and the requests your site receives. For a typical portfolio site, that’s less than the cost of a coffee per month.

But static hosting on S3 isn’t just about cost. It’s also a core building block in the AWS DevOps world: the exact same bucket you configure here will later receive build artifacts from your CI/CD pipeline, sit behind CloudFront for global edge caching, or serve as the frontend for an API powered by Lambda.

Core concept / mental model

Think of an S3 bucket as a global file cabinet in the cloud—a flat container that stores your objects (files) under unique keys (paths). By default, every object is private; only you and your AWS account can read it. Enabling static website hosting turns that private cabinet into a public web server.

The mental model is simple:

  1. Bucket = the project folder
  2. Object keys = file paths (e.g., index.html, css/styles.css)
  3. Static website hosting setting = flips the switch from “private storage” to “public web server”
  4. Bucket policy = the bouncer that lets anonymous visitors in

Once hosting is enabled, S3 gives you a regional endpoint like http://my-site.s3-website-us-east-1.amazonaws.com. The bucket must have a name that matches your domain if you plan to use a custom domain later (e.g., www.example.com).

Key insight: S3 serves only static content—HTML, CSS, JavaScript, images, and other files that don’t require server-side processing. If your site needs a backend, you’ll pair S3 with Lambda and API Gateway (you’ll build that later in this track).

How it works step by step

Follow this logical sequence to go from zero to a live website:

  1. Create an S3 bucket with a globally unique name (S3 bucket names are universal across all AWS accounts).
  2. Upload your static files—at minimum, an index.html.
  3. Enable static website hosting on the bucket’s Properties tab, and specify the index document (e.g., index.html) and error document (e.g., error.html).
  4. Make the bucket public by attaching a bucket policy that grants s3:GetObject to * (everyone). Without this, visitors get 403 Access Denied.
  5. Access your site via the endpoint S3 provides once hosting is enabled.

Behind the scenes, S3 assigns a new endpoint when you turn on static hosting; this is different from the REST API endpoint you use for programmatic access. The web endpoint serves your files over HTTP, and you can later hook it up to Route 53 for a custom domain and CloudFront for HTTPS and caching.

Hands-on walkthrough

Let’s implement this with a practical example we’ll also reuse in the next lesson. We’ll create a simple landing page, upload it with the AWS CLI (or boto3), and confirm it’s live.

Step 1: Create a minimal static site

Create a folder called my-site and add an index.html and an error.html:

<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>My S3 Site</title>
</head>
<body>
    <h1>Hello from S3!</h1>
    <p>This page is hosted on Amazon S3.</p>
</body>
</html>
<!-- error.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Page Not Found</title>
</head>
<body>
    <h1>404 - Page Not Found</h1>
</body>
</html>

Step 2: Create the bucket with the AWS CLI

If you haven’t already, install and configure the AWS CLI with your credentials. Then create a bucket (replace my-unique-site-bucket with your own unique name):

aws s3api create-bucket \
    --bucket my-unique-site-bucket \
    --region us-east-1

Note: In regions other than us-east-1, you must add --create-bucket-configuration LocationConstraint=<region> to the command.

Step 3: Enable static website hosting

Use the aws s3 website command, which is a shortcut that sets both the index and error documents:

aws s3 website my-unique-site-bucket \
    --index-document index.html \
    --error-document error.html

Step 4: Upload your files

Sync your local my-site folder to the bucket:

aws s3 sync my-site/ s3://my-unique-site-bucket/

Step 5: Attach a public-read bucket policy

Create a file policy.json:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicReadGetObject",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-unique-site-bucket/*"
    }
  ]
}

Apply it:

aws s3api put-bucket-policy \
    --bucket my-unique-site-bucket \
    --policy file://policy.json

Step 6: Open your site

Find the endpoint—either in the AWS Console under the bucket’s Properties > Static website hosting, or with:

aws s3api get-bucket-website --bucket my-unique-site-bucket

Expected output includes:

{
    "IndexDocument": {
        "Suffix": "index.html"
    },
    "ErrorDocument": {
        "Key": "error.html"
    }
}

Now open http://my-unique-site-bucket.s3-website-us-east-1.amazonaws.com in your browser. You should see your landing page—that’s your static site live on S3!

Automating with boto3 (bonus)

For a DevOps workflow, you might script this with Python. Here’s a minimal boto3 example that uploads a file and enables website hosting:

import boto3

BUCKET = "my-unique-site-bucket"
s3 = boto3.client("s3", region_name="us-east-1")

# Upload with public-read ACL (alternative to policy)
s3.upload_file(
    "index.html", BUCKET, "index.html",
    ExtraArgs={"ContentType": "text/html", "ACL": "public-read"}
)

# Ensure website hosting is on
s3.put_bucket_website(
    Bucket=BUCKET,
    WebsiteConfiguration={
        "IndexDocument": {"Suffix": "index.html"},
        "ErrorDocument": {"Key": "error.html"}
    }
)

print(f"Site live at http://{BUCKET}.s3-website-{s3.meta.region_name}.amazonaws.com")

Compare options / when to choose what

Not every static site needs the same treatment. Here’s how S3 static hosting stacks up against other common approaches:

Option Cost Scalability HTTPS by default Best for
S3 static website hosting Low (pay per GB/requests) Excellent No (needs CloudFront) Simple sites, quickly, with Python automation
S3 + CloudFront Slightly higher Global CDN Yes Production sites, custom domains, HTTPS requirement
GitHub Pages Free Good Yes Open-source projects, small personal sites
EC2 + nginx Higher (pay for instance) Manual scaling Yes (with certs) Sites needing server-side code
Amplify / Netlify Free tier available Good Yes Modern frontend apps with CI/CD

Choose plain S3 when you need a quick, low-cost host and don’t mind an http:// URL or using a subdomain. Choose S3 + CloudFront when you need HTTPS, custom domains, and global edge caching—you’ll learn that setup later in this track. Choose GitHub Pages when your code is open source and you want zero configuration. Avoid EC2 for purely static content unless you already run a server for other reasons—it’s overkill.

For a DevOps-focused pipeline, S3 is the perfect artifact store: your CI/CD process builds a static site and then aws s3 sync deploys it, exactly as we did manually here.

Troubleshooting & edge cases

Even with a simple setup, a few issues bite developers. Here are the most common failures and exactly how to fix them:

  • 403 Access Denied when you open the site — Most likely you never attached a public-read bucket policy, or you used the wrong Resource ARN. Double-check that the Resource matches your bucket name and includes /*. Then wait a few seconds for the policy to propagate.
  • 404 Not Found on the root URL — You might have forgotten to enable static website hosting, or your index document’s name doesn’t match the IndexDocument suffix. Verify the upload: aws s3 ls s3://my-unique-site-bucket/ and that index.html is at the root (not in a subfolder).
  • Endpoint not working at all — You may be using the REST endpoint (s3.amazonaws.com/bucket) instead of the website endpoint (bucket.s3-website-region.amazonaws.com). The web endpoint only responds after hosting is enabled.
  • HTTPS is missing — S3 static hosting doesn’t support HTTPS directly. If you need it, put CloudFront in front—you’ll cover this in a later lesson. For now, use http:// for testing.
  • Bucket name already taken — S3 names are globally unique. Try appending your initials or a random number, like my-site-bucket-2025.
  • aws s3 sync doesn’t delete old files — By default it only adds/updates. Add --delete to match the local folder exactly, which is critical during CI/CD deployments.

Pro tip: always test with curl first—curl http://my-unique-site-bucket.s3-website-us-east-1.amazonaws.com — to see the raw HTTP status before debugging browser cache issues.

What you learned & what's next

You’ve mastered the core idea behind hosting a static website on S3: creating a bucket, uploading files, enabling website hosting, and opening access with a bucket policy. You also practiced both a manual CLI workflow and a boto3 automation script—skills that map directly to real DevOps deployments.

Now you understand how S3 serves static content, and you’ve achieved the practical objective of getting a live site online. The next lesson in our track will take this further: you’ll attach a custom domain via Route 53, enable HTTPS with CloudFront, and build a CI/CD pipeline that deploys your static site automatically whenever you push to GitHub. The bucket you created here will become the target of that pipeline, so keep it ready.

You’re no longer just a Python developer who writes code—you’re deploying production infrastructure with AWS. Let’s keep building.

Practice recap

Next, try a small exercise: create a new bucket and deploy a simple one-page site entirely with boto3 in a Python script, including the bucket policy and website configuration. Then test what happens when you request a missing file—does your error document show up? This will solidify the concepts before you move on to custom domains and HTTPS with CloudFront.

Common mistakes

  • Forgetting to make the bucket public—you get 403 Access Denied because the default is private.
  • Using the REST API endpoint instead of the website endpoint—the former returns XML, not your site.
  • Uploading files into a subfolder but setting the index document to index.html—the root must contain the index file.
  • Omitting --create-bucket-configuration when creating a bucket outside us-east-1, causing a IllegalLocationConstraintException.

Variations

  1. Use aws s3 sync with --delete for a full CI/CD deployment that mirrors your local build folder exactly.
  2. Set individual file ACLs to public-read via --acl public-read instead of a bucket policy—simpler for small projects, less manageable for many files.
  3. Combine S3 with CloudFront and a custom domain to get HTTPS, edge caching, and global low latency—core pattern for production static sites.

Real-world use cases

  • Deploy a React or Vue.js frontend built via CI/CD to an S3 bucket, then serve it globally via CloudFront.
  • Host a static documentation site (like Sphinx or MkDocs) for an open-source Python project at low cost.
  • Serve a lightweight portfolio or marketing landing page with zero server maintenance for less than a dollar per month.

Key takeaways

  • S3 static website hosting transforms a private storage bucket into a public web server with just a few settings.
  • You must enable website hosting, set an index document, and attach a public-read bucket policy for the site to work.
  • The website endpoint differs from the REST API endpoint—always use bucket.s3-website-region.amazonaws.com in the browser.
  • S3 serves only static files; pair with Lambda and API Gateway for dynamic content.
  • Plain S3 is cheap and simple, but for HTTPS and custom domains you need CloudFront.
  • The aws s3 sync command, with --delete, is the ideal deployment mechanism for CI/CD pipelines.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.