You are serving a static site from an S3 bucket. What is the difference between S3's static website endpoint and putting CloudFront with Origin Access Control in front of the bucket's REST endpoint, and which do you choose?
answer
- two hostnames, two behaviours
- one of them has no TLS
- one of them needs a public bucket
- signed origin requests keep it private
- index documents stop at the root
basics
~20 sThe S3 website endpoint is HTTP-only and needs a public bucket, but it maps directory paths to index documents and serves custom error pages. CloudFront with Origin Access Control keeps the bucket fully private, adds HTTPS on your own domain and caching, but does no directory-index mapping below the root.
solid answer
~50 sThe website endpoint is a separate hostname with different behaviour from the REST endpoint: it serves an index document for any directory-style path, returns your custom error document, honours redirect rules — and it is plain HTTP only, with a bucket that must be publicly readable. That means Block Public Access off and a policy granting `s3:GetObject` to everyone, which most organisations will not accept. The CloudFront route points the distribution at the bucket's REST endpoint with Origin Access Control, so CloudFront signs its origin requests with SigV4 and the bucket stays private with Block Public Access on. You get TLS on a custom domain, edge caching, WAF and access logs. The gotcha is that the REST endpoint has no notion of index documents below the root, so a request for `/docs/` returns an error unless you rewrite the URI at the edge. For anything public-facing I take CloudFront and solve the rewrite.
code
bash · 6 lines# Private bucket behind CloudFront: anonymous access is denied
curl -s -o /dev/null -w '%{http_code}\n' \
https://example-bucket.s3.us-east-1.amazonaws.com/index.html
# The same object served through the distribution
curl -s -o /dev/null -w '%{http_code}\n' https://example.com/index.htmlgo deeper
Know that a bucket has two different hostnames — the S3 API endpoint and the static website endpoint — and that only the website endpoint serves an index document for a bare directory path.
Explain that the website endpoint is HTTP-only and requires a public bucket, while CloudFront with OAC signs origin requests so the bucket can stay private, and name the index-document gap that comes with the switch.
Show what you actually configure end to end: the bucket policy scoped to the distribution, Block Public Access left on, the edge rewrite for directory paths, and custom error responses replacing the bucket's error document.
Own the standard — a private-origin-by-default rule for every static site, with TLS, WAF and logging attached at the edge, so no team is left arguing for an exception to Block Public Access.
## Two different endpoints on the same bucket A bucket answers on two distinct hostname families, and they are not interchangeable: - **The REST endpoint**, `bucket-name.s3.<region>.amazonaws.com`. This is the S3 API. It speaks HTTPS, expects a SigV4 signature unless the object is public, returns errors as XML, and has no concept of an index page: a request for a key that does not exist is simply a missing key. - **The website endpoint**, `bucket-name.s3-website-<region>.amazonaws.com` (some Regions use a dot instead of the dash). This is a small web server in front of the same objects. It supports an index document, an error document, and routing rules for redirects — and it is **HTTP only**. There is no TLS on the website endpoint, and it requires the objects to be publicly readable. ```bash # Directory path on the REST endpoint: no index-document mapping curl -I https://example-bucket.s3.us-east-1.amazonaws.com/docs/ # Same path on the website endpoint: serves docs/index.html curl -I http://example-bucket.s3-website-us-east-1.amazonaws.com/docs/ ``` ## Why the website endpoint alone is rarely acceptable To use it you must turn off Block Public Access and attach a bucket policy allowing anonymous `s3:GetObject`. Two consequences follow. First, no HTTPS — you cannot serve a modern site over plaintext, and browsers and search engines both penalise it. Second, an intentionally public bucket is a permanent audit finding in most organisations, and the same relaxed setting is one careless policy edit away from exposing objects you did not mean to publish. ## What Origin Access Control changes OAC is CloudFront's mechanism for reaching a *private* origin bucket. CloudFront signs each origin request with SigV4 using a service principal, and the bucket policy grants access to that distribution only — commonly scoped with `aws:SourceArn` naming the distribution. The bucket keeps Block Public Access enabled and has no public policy at all. Anonymous requests straight to the REST endpoint are denied; only CloudFront can read it. The rest of the package comes with it: TLS with an ACM certificate on your own domain, HTTP/2 and HTTP/3, edge caching that removes most origin requests, WAF in front, and access logs. OAC superseded the older Origin Access Identity, and one practical reason to prefer it is that OAC works with buckets encrypted using SSE-KMS, which OAI did not. One configuration detail matters: **OAC applies to the REST endpoint, not the website endpoint.** A website endpoint is treated as a generic custom origin — it cannot receive a signed request and it only speaks HTTP — so pointing CloudFront at it forfeits both the private bucket and the encrypted origin hop. ## The index-document gap Because the REST endpoint knows nothing about index documents, a static site behind CloudFront and OAC breaks on directory-style URLs. The distribution's default root object covers `https://example.com/` only; `https://example.com/docs/` maps to the key `docs/` which does not exist, so the viewer gets an error. The standard fix is a small rewrite at the edge that appends `index.html` to URIs that end in a slash or carry no file extension. It is a few lines of configuration, but it is the single most common reason a freshly migrated site "works at the root and 404s everywhere else". Custom error pages need attention for the same reason: with a private origin you configure the distribution's custom error responses (mapping a 403 or 404 to your own error page) rather than relying on the bucket's error-document setting, which only the website endpoint honours. ## Choosing ``` Public site, custom domain, HTTPS required -> CloudFront + OAC over the REST endpoint Redirect rules and index docs, no TLS needed -> website endpoint (rare, and usually internal) Single-page app with client-side routing -> CloudFront, error responses mapped to index.html Bucket must never be public (policy rule) -> CloudFront + OAC is the only option ``` For almost every production case the answer is CloudFront with OAC. The website endpoint survives as a quick internal or throwaway host, and as the thing to reach for when you genuinely need S3's redirect routing rules. ## Saying it well Lead with the property that actually decides it: the website endpoint requires a public bucket and gives you no TLS. Then name what you give up by moving to the REST endpoint — index-document mapping — and how you get it back. That sequence shows you have actually shipped a site this way rather than read about it.
- A site behind CloudFront and OAC works at the root but returns an error for /docs/. Why?The origin is the S3 REST endpoint, which has no index-document behaviour. The distribution's default root object only applies to the root path, so /docs/ resolves to the key docs/, which does not exist. Fix it by rewriting the URI at the edge — append index.html when the path ends in a slash or has no extension — rather than by switching the origin to the website endpoint.
- Why can't you use Origin Access Control with the S3 website endpoint?OAC works by signing origin requests with SigV4 against the S3 API. The website endpoint is not the API — it is an HTTP-only web server that accepts unauthenticated requests and rejects nothing else — so CloudFront treats it as a generic custom origin. Using it means the bucket must stay public and the origin hop is plaintext, which defeats both reasons to use OAC.
- How does this change for a single-page application with client-side routing?Any deep link must reach the app shell rather than a missing key. Configure custom error responses on the distribution so that a 403 or 404 from the origin returns /index.html with a 200 status, letting the client router resolve the path. Keep the hashed asset files on a long cache and index.html on a short one, so a deploy is visible immediately without re-fetching every asset.
saying these in an interview costs you the question
- Thinks the website endpoint supports HTTPS
- Points CloudFront at the website endpoint while claiming the bucket is private
- Assumes the default root object applies to every directory
- Says OAI and OAC are interchangeable today
- Leaves Block Public Access off after adding CloudFront