Continuing from where I left off with S3 fundamentals, today's focus was turning a plain bucket into an actual live website using S3's static website hosting feature — no EC2 instance, no server management, just a bucket serving HTML directly over the web.
Topics Covered
- Static website hosting configuration
- Index and error documents
- Public access blocking
- Bucket policies for public reads
- Testing the live endpoint
1. Enabling Static Website Hosting
By default, an S3 bucket is just object storage — it doesn't know how to behave like a website. Enabling static website hosting (found under the bucket's Properties tab) turns it into one by telling S3 two things: which file to serve when someone visits the root URL, and which file to show if a requested page doesn't exist.
Once enabled, S3 generates a dedicated website endpoint in the form:
http://<bucket-name>.s3-website.<region>.amazonaws.com
For this lab, I set up the bucket firststaticsite-605365943139-eu-north-1-an in eu-north-1, uploaded a landing page template, and had it live at that endpoint within minutes.
Live demo: http://firststaticsite-605365943139-eu-north-1-an.s3-website.eu-north-1.amazonaws.com/
2. Index and Error Documents
Two settings drive how the site behaves:
-
Index document — the file served when someone visits the root of the site (almost always
index.html). - Error document — an optional custom page shown when a requested file doesn't exist, instead of S3's default XML error.
Getting these right matters more than it seems — a missing or misconfigured index document is one of the most common reasons a freshly enabled static site fails to load.
3. Public Access Block
By default, AWS blocks public access to every new bucket as a safety measure — which makes sense for general storage, but breaks a static website, since visitors need to reach the files without any AWS credentials.
To fix this, Block Public Access has to be explicitly turned off at the bucket level (Permissions tab). This is a deliberate extra step AWS added specifically to prevent people from accidentally exposing private data — so it's a conscious trade-off, not a default you stumble into.
4. Bucket Policy for Public Reads
Turning off Block Public Access alone isn't enough — the bucket also needs an explicit bucket policy granting read access to everyone. Something like:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::firststaticsite-605365943139-eu-north-1-an/*"
}
]
}
This grants s3:GetObject (read-only) to anyone, scoped strictly to objects inside this one bucket — it doesn't expose anything else in the account.
5. Testing the Live Endpoint
Once hosting is enabled, public access is unblocked, and the policy is in place, the site becomes reachable by anyone with the URL — not just the account owner logged into the AWS console. That last part is an easy trap: a page can look "live" while you're logged in and still fail for an outside visitor if permissions aren't fully open. Testing in an incognito window (or asking someone else to open the link) is the real test of whether the site is actually public.
Summary
Static website hosting is a good next step after learning buckets and objects, because it forces you to actually deal with the permission model instead of just uploading files. The distinction between "the object exists in the bucket" and "the object is publicly reachable" is the core lesson here, and it's one that carries over directly into more advanced AWS work later on — IAM policies, CloudFront distributions, and beyond.
Top comments (1)
The bit about "blocked public access" and "the object is publicly reachable" being two different questions is the part most people skip on the way to a green bucket policy. The website endpoint and the REST endpoint don't behave the same either: static hosting only answers on the s3-website region endpoint, so a curl against the ordinary bucket URL can 403 on the very same object that renders fine in a browser — a confusing dead end until you notice you're interrogating a different hostname.
Did you put a CloudFront distribution in front of it afterwards, or leave the bucket endpoint public? I ask because the certificate step is where a lot of these stall: CloudFront will only hand out a cert for an alternate domain in us-east-1, which surprises people building in eu-north-1.