Skip to content
AWS aws storage 6 min read

Hosting a Static Website on S3

Amazon S3 (Simple Storage Service) is best known for storing files, but it can also serve those files straight to a browser as a website. If your site is “static” — meaning plain HTML, CSS, JavaScript, and images with no server-side code — you can host it from a bucket without running a single server. This is one of the cheapest and simplest ways to put a site online, and it is a great fit for personal pages, project landing pages, single-page apps (SPAs), and documentation. In this page you will enable S3 static website hosting, find your website endpoint, and learn the one big limitation that pushes real production sites toward CloudFront.

What “static website hosting” actually means

Every S3 bucket already lets you fetch objects over the REST API endpoint. Static website hosting is a separate feature you turn on per bucket that changes how the bucket behaves like a web server. When enabled, the bucket gets a special website endpoint and gains two web-friendly behaviors:

  • Index document — if someone requests a folder-style URL (like /about/), S3 returns the configured index file (usually index.html) instead of an error.
  • Error document — if a requested key (an object’s name/path) is missing, S3 returns a custom error page (like error.html) with an HTTP 404 status instead of an ugly XML error.

These two behaviors are the difference between a bare file store and something that feels like a website.

When to use raw S3 hosting (and when not to)

Raw S3 website hosting is enough when all of these are true:

  • Your content is fully static (no backend, no server-side rendering).
  • You are fine serving over HTTP only (no https://).
  • You do not need a custom domain with a valid TLS certificate.
  • You do not need a content delivery network (CDN) for global caching/speed.

It is not enough the moment you need HTTPS, a custom domain like www.example.com with a padlock, edge caching, or fine-grained access control. For those, you front the bucket with Amazon CloudFront — covered below and in the DNS/CDN section.

Enabling static website hosting

You first create a bucket, upload your files, allow public reads, then flip on the website feature.

Console steps

  1. Open the S3 console and click your bucket (e.g. my-site-devcraftly).
  2. Upload your files, making sure an index.html exists at the bucket root.
  3. Go to the Properties tab, scroll to Static website hosting, and click Edit.
  4. Choose Enable, set Hosting type to Host a static website.
  5. Enter Index document = index.html and Error document = error.html.
  6. Click Save changes. The panel now shows your Bucket website endpoint URL.
  7. The bucket is still private by default. Go to Permissions, turn off Block all public access for this bucket, then add a bucket policy (below) granting public read.

Warning: Disabling Block Public Access makes every object in the bucket world-readable. Only do this on a bucket that holds public website assets — never on a bucket with private data. Put secrets in a different bucket.

CLI steps

Tag your CLI blocks with the real AWS CLI v2 commands. First enable the website configuration:

aws s3 website s3://my-site-devcraftly/ \
  --index-document index.html \
  --error-document error.html

Then attach a bucket policy that allows anyone to read (s3:GetObject):

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

Upload (or sync) your site files:

aws s3 sync ./public s3://my-site-devcraftly/ --delete

Output:

upload: public/index.html to s3://my-site-devcraftly/index.html
upload: public/error.html to s3://my-site-devcraftly/error.html
upload: public/css/site.css to s3://my-site-devcraftly/css/site.css

Finding and testing your website endpoint

The website endpoint is different from the normal REST endpoint and follows a region-specific pattern:

http://<bucket-name>.s3-website-<region>.amazonaws.com
http://<bucket-name>.s3-website.<region>.amazonaws.com   (newer regions use a dot)

For our example in us-east-1:

curl -I http://my-site-devcraftly.s3-website-us-east-1.amazonaws.com

Output:

HTTP/1.1 200 OK
x-amz-id-2: 8nE7...vQ==
x-amz-request-id: 7F3A1B2C4D5E6F70
Content-Type: text/html
Server: AmazonS3

Notice the URL starts with http://, not https://. That is the central limitation.

The HTTPS gotcha: why production sites add CloudFront

The S3 website endpoint is HTTP-only. It does not serve TLS (Transport Layer Security, the encryption behind https://), and you cannot attach a custom-domain certificate to it. Modern browsers flag plain HTTP as “Not secure”, and most users (and search engines) expect HTTPS. You also get no CDN caching, so far-away users wait longer.

The production pattern is to keep S3 as the origin (the source of files) and put Amazon CloudFront in front of it:

  • CloudFront terminates HTTPS using a free certificate from AWS Certificate Manager (ACM).
  • It maps your custom domain (www.example.com) via Route 53 (AWS’s DNS service).
  • It caches content at edge locations worldwide for speed.
  • You lock the bucket fully private and let CloudFront read it through Origin Access Control (OAC), so users can only reach files through CloudFront.
NeedRaw S3 website endpointS3 + CloudFront + ACM
HTTPS / TLSNo (HTTP only)Yes
Custom domain with certNoYes
Global edge caching (CDN)NoYes
Bucket can stay privateNo (must be public)Yes (via OAC)
Setup effortMinutesA bit more
Best forQuick demos, internal/static stuffAnything user-facing

Tip: When you use CloudFront with OAC, do not enable the S3 website endpoint at all — point CloudFront at the bucket’s REST endpoint and keep Block Public Access fully on. The website endpoint is only for the standalone, public-bucket approach.

The full CloudFront + ACM + Route 53 walkthrough lives in the DNS/CDN section of these docs.

SPA routing note

For single-page apps (React, Angular, Vue) that handle routing in the browser, deep links like /dashboard would 404 because no such object exists. With raw S3 hosting, a common trick is to set the error document to index.html so missing paths fall back to your app shell. With CloudFront, you instead use a custom error response mapping 403/404 to /index.html with a 200 status.

Cost note

S3 website hosting is extremely cheap. As of 2026, Standard storage is about $0.023 per GB per month, and data transfer out is roughly $0.09 per GB after the first free tier allowance. A small site getting modest traffic typically costs a few cents to a couple of dollars per month. Adding CloudFront often lowers egress cost (its transfer-out rates are similar and caching reduces origin hits) while adding HTTPS and speed.

Best Practices

  • Keep website assets in a dedicated public bucket; never mix them with private data.
  • For anything user-facing, front S3 with CloudFront + ACM to get HTTPS, a custom domain, and caching.
  • Use aws s3 sync --delete in your deploy pipeline so removed files disappear from the bucket too.
  • Set sensible Cache-Control headers on objects so browsers and CloudFront cache correctly.
  • Enable versioning on the bucket so a bad deploy can be rolled back.
  • For SPAs, configure the index-document fallback (raw S3) or a custom error response (CloudFront) for client-side routing.
Last updated June 15, 2026
Was this helpful?