Hosting a Static Site on S3 + CloudFront: Architecture and Economics
A surprising number of websites — marketing sites, docs, blogs, landing pages, even whole single-page apps — don't need a server at all. Amazon S3 plus CloudFront will serve them faster, more reliably, and more cheaply than a box you have to patch. This is the reference architecture we reach for, the details that actually matter (TLS, caching, security headers, SPA routing), what it costs, and the cases where it's the wrong tool.
The shape of it
The pattern is small and boring in the best way: store the files in S3, put a CDN in front, point your domain at the CDN.
- Amazon S3 holds the built site (HTML, CSS, JS, images). The bucket stays private — you do not use the legacy "S3 website endpoint" or make objects public.
- Amazon CloudFront sits in front as the CDN: it terminates TLS, caches content at edge locations worldwide, and is the only thing allowed to read the bucket.
- Origin Access Control (OAC) locks the bucket so it only answers requests signed by your CloudFront distribution. The public can reach the site only through CloudFront.
- AWS Certificate Manager (ACM) issues a free TLS certificate. CloudFront requires the cert in the us-east-1 region regardless of where your bucket lives — a classic gotcha.
- Route 53 (or any DNS provider) maps your apex and
wwwto the distribution. With Route 53 you use an ALIAS record so the apex points straight at CloudFront, no redirect hop.
The details that actually matter
HTTPS and redirects
Set the CloudFront viewer policy to redirect HTTP to HTTPS, and use an ACM certificate that
covers both your apex (example.com) and a wildcard (*.example.com) if you'll have
subdomains. That one wildcard cert then serves the marketing site, a blog subdomain, docs, and so on.
Caching — the part people get wrong
A CDN is only as good as its cache rules, and static sites have two very different kinds of file:
- Fingerprinted assets (
app.9f3a1c.js, hashed images) never change for a given name — cache them immutably for a year (Cache-Control: public, max-age=31536000, immutable). - HTML and entry points change on every deploy — give them a short or no-cache policy so visitors get the new version promptly, and lean on a deploy-time invalidation (below).
⚠️ Watch out for caching dynamic responses. If your "static" site ever serves
per-request payloads (server-rendered fragments, signed URLs), a too-aggressive shared cache will hand one
visitor's response to another. Mark anything per-request private / no-store and reserve long
TTLs for genuinely static, fingerprinted files. Getting this wrong is the most common production incident
with CDN-fronted sites.
Pretty URLs and SPA routing
Two routing needs come up constantly. For "directory" URLs (/about/ serving
/about/index.html), use a small CloudFront Function to rewrite the request. For a single-page
app, route 403/404s back to index.html so the client-side router can take over. Both are a few
lines at the edge — no server required.
Security headers
Attach a CloudFront response-headers policy to add HSTS, X-Content-Type-Options:
nosniff, a sensible Content-Security-Policy, Referrer-Policy, and frame
protection. It costs nothing and meaningfully hardens the site — the kind of default we'd insist on in any
review (see Security in the Age of AI).
Deploys
A deploy is two commands: aws s3 sync the built files to the bucket, then create a CloudFront
invalidation so the edge serves the new HTML immediately. Because invalidation paths beyond
a monthly free allotment cost a small amount, invalidate narrowly (e.g. /* only when needed, or
specific paths) rather than reflexively.
What it costs
This is the punchline: for a normal-traffic site, pennies to a few dollars a month. S3 storage for a static site is a rounding error; CloudFront has a generous always-free tier and then bills fractions of a cent per GB and per 10,000 requests; ACM certificates are free. There are no instances to run 24/7, no patching, no autoscaling group — you pay almost purely for the bytes you actually serve. A site that would cost real money on an always-on server often costs less than a cup of coffee here.
Why it's also faster and more reliable
Beyond cost, you inherit a global CDN's edge network (low latency everywhere), effectively serverless scaling (a traffic spike is CloudFront's problem, not yours), and a tiny attack surface — there's no application server or database to compromise, just files behind a locked-down bucket. Fewer moving parts is itself a reliability strategy.
When not to use it
Static hosting is the wrong choice when you genuinely need server-side logic per request: user authentication and sessions, server-side database queries, form processing, or true server-side rendering of personalized pages. You can stretch quite far with edge functions and serverless APIs (Lambda behind API Gateway or a Lambda@Edge/CloudFront Function), but if most of your pages are dynamic and personalized, a proper application platform is the right call. The sweet spot for S3 + CloudFront is content that's the same for everyone: sites, docs, blogs, and front-ends that talk to APIs from the browser.
The takeaway
For the large category of sites that are fundamentally content, S3 + CloudFront is hard to beat: fast, globally distributed, secure by construction, and cheap enough that the bill barely registers. Keep the bucket private behind OAC, get your cache rules right, add security headers, and automate the sync-and-invalidate deploy — and you've got a production website with almost nothing to operate.