Amazon S3 can serve a complete static website (HTML, CSS, JavaScript, images) with no servers to manage, and it costs pennies for small sites. It is also the most popular first project on AWS, because it touches several core ideas at once: buckets, objects, permissions and public access. This guide walks through the whole process and explains why each step exists.
What you need
- An AWS account (or a Cloud Arena sandbox, which gives you one with nothing on your bill).
- An
index.htmlfile. Anerror.htmlpage is optional but nice.
Step 1: Create the bucket
Open the S3 console and choose Create bucket. Bucket names are global across all AWS accounts, so pick something unique like bakery-site-2026-yourname. If you later want to serve it directly from a domain without CloudFront, the bucket name must match the domain (www.example.com).
Choose the Region closest to your visitors. Leave Block all public access turned on for now; we will open exactly what is needed later.
Step 2: Upload your files
Upload index.html and any assets to the bucket root. Keep the folder structure your HTML expects: if a page references css/style.css, upload it under the css/ prefix. S3 sets the Content-Type from the file extension when you upload through the console; when using scripts, set it explicitly so browsers render pages instead of downloading them.
Step 3: Enable static website hosting
In the bucket's Properties tab, edit Static website hosting, enable it, and set the index document to index.html (and the error document if you have one). S3 shows a website endpoint such as:
http://bakery-site-2026-yourname.s3-website-us-east-1.amazonaws.com
Open it now and you will see 403 Forbidden. That is expected: the bucket is still private.
Step 4: Allow public reads, and only reads
Two things currently block visitors:
- Block Public Access settings on the bucket (and possibly at account level) prevent any public policy from taking effect.
- There is no bucket policy granting anonymous read access.
In the Permissions tab, edit Block Public Access and turn off "Block all public access" for this bucket only. Then add this bucket policy (replace the bucket name):
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "PublicReadForWebsite",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::bakery-site-2026-yourname/*"
}]
}
The policy only grants s3:GetObject on objects. Visitors can read pages but cannot list, upload or delete anything. Reload the website endpoint and the site appears.
Never make a bucket public that holds anything other than website content. S3 is a frequent source of data leaks precisely because buckets were opened "temporarily".
Step 5: Add HTTPS and a custom domain with CloudFront
The S3 website endpoint only speaks HTTP. For HTTPS, a custom domain and global caching, put Amazon CloudFront in front:
- Request a free public certificate for your domain in AWS Certificate Manager, in the us-east-1 Region (CloudFront requires that Region).
- Create a CloudFront distribution with your bucket as the origin and attach the certificate and your domain as an alternate name.
- Point your domain at the distribution with a Route 53 alias record (or a CNAME at your DNS provider).
The modern, more secure pattern is to keep the bucket private and let only CloudFront read it using Origin Access Control (OAC). Then you do not need public access at all; CloudFront's own policy grant replaces the public bucket policy. Note that OAC uses the bucket's REST endpoint, so the S3 website features (index documents in subfolders, redirect rules) are handled by CloudFront settings or a small CloudFront Function instead.
Step 6: Deploy updates
From your project folder:
aws s3 sync ./site s3://bakery-site-2026-yourname --delete
If CloudFront caches your pages, invalidate changed paths (aws cloudfront create-invalidation --distribution-id ... --paths "/index.html") or use versioned file names for assets so caches never serve stale files.
Troubleshooting
| Symptom | Likely cause |
|---|---|
| 403 Forbidden on the website endpoint | Block Public Access still on, or no bucket policy, or the policy is missing /* |
| 404 Not Found | Index document name differs from the uploaded file (case matters) |
| Browser downloads the page | Wrong Content-Type on the object |
| Old content after deploy | CloudFront or browser cache; invalidate or version your files |
| Access Denied from CloudFront with OAC | Bucket policy does not grant the distribution, or the origin uses the website endpoint instead of the bucket endpoint |
We explain every cause of S3 403 errors in How to Fix S3 403 Forbidden and Access Denied.
Do it for real
Cloud Arena's free lab Host Your First Website on Amazon S3 walks you through exactly these steps in a real AWS account, checking each one automatically. Then try the incident Bakery Website Returns 403 Forbidden, where a broken site waits for you to find the cause, and Serving Static and Dynamic Content with CloudFront.
FAQ
How much does an S3 website cost?
For a small site, typically cents per month: you pay for storage, requests and data transfer out. Adding CloudFront often lowers transfer costs and its free tier covers many small sites.
Can S3 run PHP or a database?
No. S3 serves static files only. For dynamic features, call an API (for example API Gateway and Lambda) from your JavaScript.
Do I need Route 53?
Only if you want AWS to host your DNS. Any DNS provider works with CloudFront using a CNAME record.