I’ve been there—you upload an HTML file to S3, configure static website hosting, grab that endpoint URL, and all you see is a blank page and a 403 Forbidden error staring back at you. After troubleshooting for 30 minutes, I realized the issue wasn’t the HTML or my upload—it was a combination of permissions, public access settings, and bucket configuration that I missed. In this post, I’ll walk through exactly what causes this and how to fix it.

The Problem

When you visit your S3 static website endpoint (like http://my-bucket.s3-website-us-east-1.amazonaws.com), you get a 403 Forbidden error. No objects load, and even your index document doesn’t appear.

Error Type Description
403 Forbidden The server understood the request but refuses to authorize it. S3 requires explicit read permissions.
Access Denied CloudFront or S3 doesn’t have permission to read the object.

Why Does This Happen?

  • S3 Block Public Access is enabled — At the account level, the account-wide setting for BlockPublicPolicy prevents any public bucket policy from taking effect. At the bucket level, RestrictPublicBuckets blocks public access even if a policy exists.
  • No bucket policy granting GetObject to everyone — S3 is private by default. Without an explicit s3:GetObject allow for "Principal": "*", no one can read anything.
  • Static website hosting not configured — The bucket must have the static website hosting setting enabled, and you need to specify an index document (usually index.html).
  • Objects uploaded with private ACL — If you uploaded objects with a private canned ACL, the bucket policy doesn’t override it. The object itself is locked down.

The Fix

Step 1: Disable Block Public Access

First, check and disable S3 Block Public Access settings at both the account and bucket level:

# Disable at bucket level
aws s3api put-public-access-block \
  --bucket my-bucket \
  --public-access-block-configuration \
  BlockPublicAcls=false,IgnorePublicAcls=false,BlockPublicPolicy=false,RestrictPublicBuckets=false

Step 2: Add Bucket Policy for Public Read Access

Create a bucket policy that allows public s3:GetObject access:

# Save this as bucket-policy.json
cat > bucket-policy.json <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicReadGetObject",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-bucket/*"
    }
  ]
}
EOF

# Apply the policy
aws s3api put-bucket-policy \
  --bucket my-bucket \
  --policy file://bucket-policy.json

Step 3: Enable Static Website Hosting

Verify that static website hosting is enabled and the index document is set:

# Enable static website hosting
aws s3api put-bucket-website \
  --bucket my-bucket \
  --website-configuration file://website-config.json

Create website-config.json:

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

Verify the configuration:

aws s3api get-bucket-website --bucket my-bucket

Step 4: Ensure Objects Have Public Read ACL

If objects were uploaded before the policy was applied, re-upload them or change their ACL:

# Copy an object and update ACL to public-read
aws s3 cp s3://my-bucket/index.html s3://my-bucket/index.html \
  --acl public-read \
  --metadata-directive COPY

How to Run This

  1. Replace my-bucket with your actual S3 bucket name
  2. Ensure you have AWS CLI credentials configured with permissions to manage S3 bucket policies and settings
  3. Run the commands in order — disable Block Public Access first, then add the policy
  4. Upload your HTML files (or re-sync them) to the bucket
  5. Visit your static website endpoint and verify the 403 error is gone

Is This Safe?

Making your S3 bucket public for a static website is acceptable for public content—HTML, CSS, JavaScript, images. Never store sensitive data (credentials, API keys, private documents) in a public bucket. Use CloudFront in front of S3 for additional security and performance, and consider signed URLs for sensitive assets.

Key Takeaway

The 403 Forbidden error on an S3 static website is almost always caused by Block Public Access settings, a missing bucket policy, or objects without public read permissions. Check all three, and your website will be up and running.


Have questions or ran into a different S3 issue? Connect with me on LinkedIn or X.