Skip to content

Primary SAA curriculum

Static Site With CloudFront And S3

A globally fast HTTPS static website with CloudFront while S3 stores the files privately behind origin access control.

20 min read

After this, you will understand

Learn why a public website does not require a public bucket, and how caching changes both request flow and deployment strategy.

Article guideprerequisites, mental models, and concepts

Article overview

foundationCloudCertificationSecurity

Three useful mental models

In plain terms

Keep the website files in a private S3 bucket and make CloudFront the only public entrance.

Decision pressure

Learners confuse an S3 website endpoint with a private S3 origin, leave a direct path around CloudFront, or overwrite cached files without planning how browsers receive the new version.

Exam-ready model

Use an S3 bucket origin with OAC, deliver it through CloudFront, map the domain with Route 53, attach an ACM certificate, and deploy cache-safe file versions.

Think before reading

If everyone can view the website, why should the S3 bucket remain private?

The content is public through CloudFront, but a private bucket removes the direct S3 side door and makes CloudFront the controlled delivery layer.

Connected learning

These lessons add useful context to the current core lesson.
  1. 1Serverless API With Lambda And DynamoDBAWS Scenario

Concepts Covered

  • What makes a website static
  • S3 as private website-file storage
  • CloudFront distributions and edge caching
  • S3 bucket origins versus S3 website endpoints
  • Origin access control (OAC)
  • CloudFront default root object
  • Cache hits and cache misses
  • Route 53 alias records and ACM certificates
  • Cache-safe deployments with versioned assets
  • AWS Solutions Architect Associate exam (SAA-C03) recognition patterns and traps

1. Situation

Your team has built a product website from HTML, CSS, JavaScript, images, and fonts. A build process creates files such as these:

index.html
styles.18c4.css
app.a91f.js
logo.webp

The browser downloads those files and runs the JavaScript. AWS does not need to execute server-side code for each page request. That is what static site means here.

Static does not mean the site never changes. It means each request returns an already-created file rather than asking an application server to generate the page.

The website needs to:

  1. load quickly for users around the world;
  2. use https://example.com;
  3. run without web servers to patch or scale;
  4. prevent users from bypassing the public entry point and reading S3 directly; and
  5. show new releases without mixing old and new files.

The central problem is therefore not merely, "Where do we put the files?"

It is:

How do we store static files cheaply, deliver them globally, and keep the storage layer private?

2. Naive Design

Naive option 1: run a web server

An EC2 server can return static files, but it adds operational responsibilities that the request does not require.

This can work, but the server adds operating system patching, capacity planning, scaling, and instance recovery. None of those tasks make an HTML file more static.

Use a server when the request needs server-side computation. Do not introduce one merely to return files that already exist.

Naive option 2: make an S3 website endpoint public

S3 website hosting is a different architecture: its website endpoint cannot support the private OAC-protected origin required here.

S3 website hosting can serve a simple site, but it creates a different architecture from the private-origin design in this lesson.

An S3 website endpoint is the public web-hosting interface of a bucket. The endpoint itself supports only HTTP, and CloudFront treats it as a custom origin. Most importantly for this scenario, OAC cannot protect a website endpoint, so it cannot provide the private CloudFront-to-S3 relationship we need.

That makes this the wrong starting point when the requirement says:

  • keep the bucket private;
  • allow access only through CloudFront; or
  • prevent direct S3 access.

Naive option 3: add CloudFront but leave S3 public

Leaving the bucket public creates a side door around CloudFront, so the delivery layer is not enforced.

CloudFront now caches the files, but users can still bypass it if they know the S3 URL. The delivery layer is optional rather than enforced.

3. What Breaks

Follow four concrete failure stories.

Failure 1: distant users repeatedly cross the world for the same file

An S3 bucket lives in one AWS Region. Without an edge cache, a user far from that Region repeatedly fetches common files over a long network path.

The architecture needs a global cache close to users.

Failure 2: the public origin becomes a second entrance

The team adds CloudFront but leaves S3 public. Security rules, HTTPS redirects, logging choices, or future WAF rules attached to CloudFront do not apply when a user accesses S3 directly.

The architecture needs one intended public entrance.

Failure 3: a deployment mixes file generations

The team overwrites app.js, but an edge cache or browser still holds the old copy. Meanwhile, index.html expects the new JavaScript. Some users receive a page assembled from two releases.

The architecture needs a cache-aware deployment strategy, not just an upload command.

Failure 4: a single-page application returns an error on refresh

An SPA usually ships one HTML entry point, such as index.html, plus its JavaScript files. After that JavaScript loads, a client-side router can understand a URL such as /orders/42 even though the S3 bucket contains no object named orders/42. The route exists inside the SPA, but it does not exist as an S3 object.

Normal client-side navigation works because the browser has already loaded index.html and started the JavaScript application. When the user navigates to /orders/42, the client-side router renders that view inside the browser. S3 is not asked for an /orders/42 object.

A refresh or direct request is different. The browser starts by sending GET /orders/42 to CloudFront. CloudFront asks the S3 origin for that path, but no matching object exists, so S3 returns a 403 or 404. The JavaScript router never gets a chance to handle the route because the application has not loaded yet.

The distribution therefore needs deliberate SPA fallback behavior. A common approach is a CloudFront Custom Error Response that maps the relevant S3 403 or 404 to /index.html and returns HTTP 200. Once index.html loads, the client-side router reads /orders/42 from the browser URL and renders the correct view. A CloudFront Function that rewrites application routes to /index.html is another approach.

Exam trap: setting CloudFront's Default Root Object to index.html handles a request for /. It does not automatically rewrite /orders/42 or other nested SPA routes.

The route exists inside the SPA, not as an S3 object. CloudFront needs deliberate fallback or rewrite behavior for direct requests to nested routes.

4. AWS Architecture

Build the solution one responsibility at a time.

Step 1: store the build output and choose how CloudFront reaches S3

S3 is object storage. Each generated site file becomes an object in a bucket.

S3 stores the generated build files as objects; it does not execute server-side application code for each request.

One S3 bucket can be reached through two different endpoints. The regular S3 bucket endpoint is the normal object-storage interface. If you enable Static Website Hosting, S3 also provides an S3 website endpoint. When you configure the CloudFront origin—the place CloudFront fetches files from—you are choosing which of these two S3 interfaces CloudFront will contact. The files remain objects in the same kind of bucket either way.

With the regular S3 bucket endpoint, CloudFront requests objects such as index.html directly from S3. CloudFront treats this as a native S3 bucket origin, can connect over HTTPS, and can use Origin Access Control (OAC). OAC allows the intended CloudFront distribution to read the objects while the bucket remains private and direct user access stays blocked.

With the S3 website endpoint, S3 behaves more like a basic web server and provides website-specific features such as index documents, error documents, and redirects. CloudFront treats this endpoint as a custom origin, not as a private S3 origin. The website endpoint is HTTP-only and does not support OAC, so it cannot provide the private-origin relationship required here. Compare the two choices directly:

QuestionRegular S3 bucket originS3 website endpoint
Which S3 interface CloudFront callsS3 REST API endpoint; treated as a native S3 originS3 static website endpoint; treated as a custom HTTP origin
Can the bucket stay private with OAC?YesNo; OAC does not support website endpoints.
Can CloudFront use HTTPS to the S3 endpoint?Yes; CloudFront uses the S3 REST endpoint.No; the website endpoint is HTTP-only.
Best fit for this scenarioYesNo

For this scenario, choose the regular S3 bucket origin and leave S3 Static Website Hosting disabled. The intended path is User → CloudFront → OAC-authorized request → private S3 bucket. The phrase “host a static site on S3” is not enough to choose an origin—the deciding requirement is that CloudFront must be the only public entrance while the S3 bucket remains private. Takeaway: private S3 behind CloudFront means a regular S3 bucket origin plus OAC; an S3 website endpoint provides website-hosting behavior but cannot use OAC.

The files remain in S3 in both cases. The choice is which S3 interface CloudFront uses as its origin, and the private-origin requirement selects the regular bucket endpoint.

Step 2: put CloudFront in front of S3

S3 stores the website files. CloudFront sits in front of S3 and delivers those files to users. The CloudFront setup for this website is called a distribution. In that distribution, configure the private S3 bucket as the origin—the place CloudFront goes to get a file when it does not already have a cached copy.

S3 stores the files. CloudFront delivers them, serving a cached copy on a hit or fetching from the private S3 origin through OAC on a miss.

For each request, CloudFront checks whether it already has a fresh copy of the file. A cache hit means the file is available in CloudFront's cache, so CloudFront returns it directly and does not contact S3. A cache miss means the file is not cached, so CloudFront fetches it from the S3 origin and can save a copy for future requests. On that origin request, Origin Access Control (OAC) gives CloudFront permission to read the private bucket while direct user access to S3 remains blocked.

Set the distribution's Default Root Object to index.html. This tells CloudFront to return index.html when a user requests /. It does not automatically rewrite a nested SPA route such as /orders/42; that still needs the separate fallback behavior explained earlier.

Need a deeper refresher on distributions, origins, and cache behaviors? Review Amazon CloudFront.

Step 3: keep S3 private with OAC

Now enforce the relationship introduced above: users may reach the website through CloudFront, but they may not read the S3 bucket directly.

OAC makes CloudFront sign its requests to S3. The bucket policy allows the intended CloudFront distribution to read the required website objects, while S3 Block Public Access keeps anonymous public access disabled. A user who tries to bypass CloudFront and request the bucket directly is denied.

OAC authorizes CloudFront's origin request through the bucket policy; it is a trust mechanism, not another network hop.

OAC is a permission mechanism on the CloudFront-to-S3 request. It is not another server or network hop.

Step 4: add the name and HTTPS identity

CloudFront provides a domain name, but users expect a name such as example.com.

Request or import an ACM certificate that covers the custom domain and attach it to the CloudFront distribution. For CloudFront, the ACM certificate must be in us-east-1.

Create a Route 53 alias record that points the domain to the distribution. Route 53 answers the DNS question, "Where should example.com connect?" It does not carry every HTTP request through itself.

Route 53 answers the name, CloudFront serves the request, ACM proves the HTTPS identity, and S3 stores the files.

Step 5: plan deployments around the cache

Use content-hashed or otherwise versioned names for assets that can be cached for a long time:

New content gets a new asset name, while index.html tells browsers which release to load and can be refreshed independently.

The new index.html points to the new asset name. CloudFront sees a new path and fetches the new object instead of confusing it with the old cached file.

Keep index.html on a shorter cache lifetime, or invalidate that small entry file when a release must appear immediately. A CloudFront invalidation tells edge caches to discard their saved response for a path. The next request for that path returns to the origin and retrieves the current file. There is usually no reason to invalidate every versioned asset, because a changed asset already has a new name and therefore a new cache entry.

The completed architecture is:

CloudFront is the public entrance, S3 is the private origin, and DNS, TLS, OAC, and deployment controls each have separate responsibilities.

5. Request Or Data Flow

Learn three short flows: a cache hit, a cache miss, and a deployment.

A cache key is the lookup identity CloudFront uses for a cached response. For this static site, the object path is the most important part: /logo.webp and /app.42bd.js identify different cached objects.

Flow 1: cache hit

A cache hit is the fast path: CloudFront returns its fresh edge copy and S3 is not contacted.
  1. Route 53 has already resolved the domain to CloudFront.
  2. The user sends an HTTPS request to CloudFront.
  3. CloudFront finds a fresh copy under the request's cache key.
  4. CloudFront returns it without contacting S3.

This is the fast path.

Flow 2: cache miss

On a miss, CloudFront retrieves the object from the private origin, stores it at the edge, and serves the viewer.
  1. CloudFront cannot use a cached copy because the object is absent or expired.
  2. CloudFront signs the origin request through OAC.
  3. The bucket policy permits the configured distribution to read the object.
  4. S3 returns the file.
  5. CloudFront caches it according to the cache policy and response headers, then returns it to the user.

Flow 3: deployment

  1. The build creates new asset names such as app.42bd.js.
  2. The deployment role uploads the new files to S3.
  3. The new index.html references those new names.
  4. The pipeline invalidates index.html if the release cannot wait for its cache lifetime.
  5. Users receive one coherent release while old versioned assets remain available during the transition.

6. Security Controls

Keep the bucket private

Enable S3 Block Public Access and use a bucket policy that allows the intended CloudFront distribution to read the required objects. Do not grant anonymous public reads merely because the website itself is public.

The public/private boundary becomes:

public:  user -> CloudFront
private: CloudFront -> S3 through OAC
denied:  user -> S3 directly

Require HTTPS

Attach a certificate that covers the custom domain and configure CloudFront to redirect HTTP viewers to HTTPS or require HTTPS, depending on the requirement.

Limit deployment permissions

Give the deployment role only the actions and resources it needs, such as writing to the site prefix and creating an invalidation when the workflow requires one. It should not be an account administrator.

Never place secrets in frontend files

Anything downloaded by a browser can be inspected by a user. Do not embed database passwords, private API keys, or long-lived AWS credentials in JavaScript bundles or configuration files.

Add WAF only when the threat requirement calls for it

AWS WAF can attach to CloudFront when the scenario needs request filtering, rate-based rules, or managed web protections. It is not required merely to store and deliver a static file. Review CloudFront WAF Protected Web Edge for that decision.

7. Resilience Controls

S3 provides durable regional object storage, and CloudFront distributes cached copies across its edge network. That removes the need to maintain a fleet of web servers for the static content.

Caching can reduce dependence on the origin for objects that are already fresh at an edge. Do not treat the cache as a backup, however: whether CloudFront can serve an object during an origin problem depends on cache state, expiration, and error-handling configuration.

Versioned asset names make releases easier to roll forward and back. A rollback can point index.html back to the previous asset names while those objects still exist.

Consider S3 Versioning when accidental overwrite or deletion recovery matters. Use lifecycle rules to expire old releases only after the rollback window has passed.

For a single-page application, test direct navigation to nested routes. Configure a deliberate rewrite or error-response strategy rather than discovering after launch that only the home page loads.

8. Performance Controls

Treat files according to how they change.

File typeUseful caching approachWhy
Hashed JavaScript, CSS, images, and fontsLong cache lifetime; often marked immutableA content change creates a new filename
index.htmlShorter lifetime or targeted invalidationIt tells browsers which versioned assets to load
Rare unversioned filesDeliberate lifetime plus targeted invalidation when neededReusing the same path can otherwise serve stale content

Enable compression for compressible content such as HTML, CSS, and JavaScript. Optimize image size and format before delivery; a CDN moves an oversized image quickly, but the user still downloads an oversized image.

Keep the cache key small. Forward cookies, headers, or query strings only when they genuinely change the response. Unnecessary variation creates separate cache entries for the same file and lowers the cache-hit ratio.

9. Cost Controls

This architecture replaces always-running web servers with usage-based storage and delivery services. The important cost drivers include:

  • S3 storage and requests;
  • CloudFront requests and data transfer;
  • Route 53 hosted-zone and DNS usage; and
  • retained build versions and logs.

A higher cache-hit ratio means fewer origin requests. Long lifetimes work especially well for uniquely named assets because a new release creates new names rather than overwriting cached content.

Use lifecycle policies to remove old build artifacts after the rollback period. Scope logs and retention to the operational need.

Prefer versioned filenames for frequently updated assets. Invalidations are useful when an existing path must disappear or refresh before expiry, but broad, frequent invalidations add work and can add cost.

10. Exam Variants

If the question says...Think...Why
"Static website," "global users," or "low latency"CloudFront with an S3 originS3 stores the objects; CloudFront caches them near users
"Prevent direct access to the bucket"Private S3 bucket origin + OAC + bucket policyCloudFront becomes the enforced public entrance
"Custom domain over HTTPS"CloudFront + ACM certificate in us-east-1 + Route 53 aliasThe certificate proves the domain identity; DNS points the name to the distribution
"Users receive stale files after deployment"Versioned filenames, correct cache headers, or targeted invalidationDeployment must account for edge and browser caches
"Lowest operational overhead"S3 and CloudFront, not an EC2 web-server fleetStatic files do not require request-time server compute
"OAC with an S3 website endpoint"Change to a regular S3 bucket originWebsite endpoints are custom origins and do not support OAC
"Server-side code must run for each request"Static hosting alone is insufficientAdd an application or API compute layer for the dynamic work

The recognition pattern to remember is:

global static delivery       -> CloudFront
durable object storage       -> S3
no direct bucket access      -> OAC + private bucket
friendly DNS name            -> Route 53 alias
HTTPS for the custom domain  -> ACM certificate for CloudFront
safe cache updates           -> versioned assets + deliberate HTML caching

11. Common Traps

TrapBetter reasoning
"The site is public, so the bucket must be public."The content can be public through CloudFront while the origin stays private.
"S3 website hosting is required for every S3-backed site."A private CloudFront design uses the regular S3 bucket origin, not the website endpoint.
Drawing user -> Route 53 -> CloudFront as the HTTP flowRoute 53 answers DNS; the browser then connects to CloudFront.
Treating OAC as a proxy serverOAC signs and authorizes CloudFront's origin request; it is not a separate hop.
Putting the ACM certificate in the S3 bucket's RegionA certificate attached to CloudFront must be requested or imported in us-east-1.
Overwriting app.js and assuming every cache updates immediatelyGive changing assets new names or deliberately invalidate reused paths.
Forwarding every cookie, header, and query stringForward only values that change the response so static files share cache entries.
Expecting a CDN to hide huge assetsOptimize the files as well as their delivery path.

Final Mental Model: One-Minute Review

Final Architecture Map

The public website ends at CloudFront. The S3 bucket remains a private origin, while versioned assets allow long cache lifetimes without stale deployments.

TermExact jobRemember it as
Route 53 aliasAnswers DNS so the custom domain resolves to CloudFrontThe address book
ACM certificateProves the site's identity and enables HTTPS at CloudFrontThe storefront identity certificate
CloudFrontPublicly delivers and caches website files close to usersThe global storefront
OACSigns CloudFront requests authorized by the private S3 bucket policyThe storefront's warehouse badge
Private S3 bucket originStores the built HTML, CSS, JavaScript, images, and fontsThe locked warehouse
Versioned assetsGive changed files new names so caches can keep long lifetimes safelyNew labels for each product version
index.html cache strategyControls how quickly browsers discover the newest asset namesThe storefront's current catalog
The site is public through CloudFront, not through S3; DNS, TLS, OAC, and versioned assets make the delivery path usable and controlled.

The website is public through CloudFront, not through S3. Route 53 supplies the name, ACM supplies the HTTPS identity, OAC protects the origin relationship, and versioned assets keep deployments compatible with caching.

Use Amazon CloudFront for deeper coverage of distributions, origins, behaviors, cache keys, and invalidations. Review Amazon S3 for object storage and bucket controls, Amazon Route 53 for DNS routing, and Identity Policies vs Resource Policies for the bucket-policy mental model.

Continue to Serverless API With Lambda And DynamoDB when the browser needs to call a dynamic backend rather than download only static files.

Official AWS references:

Finished reading?

Your reading history is saved in this browser so you can continue later.

Recommended Next

Serverless API With Lambda And DynamoDBAWS Architecture Scenarios24 min read

This applies the foundation mental models to a real architecture decision instead of a service inventory.

Optional exploration

These links add context, but they do not replace the recommended next lesson.

Arcflow Plus is coming — review drills, research breakdowns, more AI. Get one email at launch.