Spinning up a portfolio or product‑landing page shouldn’t feel like configuring a nuclear reactor. Yet “simple” sites can snowball into DevOps headaches: SSL certificates, DNS records, caching rules, redirect chains, regional restrictions—each step adds another late‑night rabbit hole. The good news? In 2025, Amazon Web Services has polished its tooling so you can launch a secure, globally cached static website on AWS S3 with CloudFront and Route 53 in a single afternoon—and sleep easy knowing it scales to millions of hits for cents on the dollar.
This guide—How to Host a Secure Static Website on AWS S3 with CloudFront and Route 53 in 2025—walks you through every part of the puzzle with battle‑tested commands, architectural gotchas, and cost checkpoints. By the end you’ll have:
- An S3 bucket hardened against public listing and accidental deletes
- A CloudFront distribution serving your content over HTTPS with HTTP/3
- Free, auto‑renewing SSL certificates via AWS Certificate Manager (ACM)
- Route 53 DNS records wired to your apex domain and
www
subdomain - CI/CD snippets for GitHub Actions so updates deploy on every push
- A caching policy that scores green in Core Web Vitals without starving visitors of fresh content
We’ll drop the full title again a bit later because SEO robots love it and humans often skim.
Why Choose the S3 + CloudFront Stack in 2025?
- Ridiculously cheap – S3 Standard storage is $0.023/GB‑month in most Regions; CloudFront’s new Tier‑1 pricing starts at $0.007 per GB egress for North America and Europe.
- Global edge network – Over 400 edge locations keep TTFB low from Sydney to São Paulo.
- Zero‑maintenance TLS – ACM auto‑renews certificates and integrates directly with CloudFront.
- Security first – Block public S3 access, serve via origin access control (OAC), and deny all HTTP; you’re protected from bucket leaks.
- Modern protocols – CloudFront added HTTP/3 and QUIC in stable GA last year.
- Seamless scalability – No servers, no autoscaling groups, no patch Tuesday, ever.
If you need server‑rendering or heavy authentication, you’ll look elsewhere; but for marketing pages, documentation hubs, portfolio sites, or Jamstack SPA front‑ends, this setup rules 2025.
High‑Level Architecture
markdownCopyUser ── HTTPS/HTTP3 ─▶ CloudFront ── S3 OAC ─▶ Private S3 Bucket
└─ Route 53 ─▶ example.com
- S3 bucket stores
index.html
, image assets, and bundles—private. - Origin Access Control (OAC) signs CloudFront requests to S3; no other traffic reaches the bucket.
- CloudFront caches objects at edge, handles TLS, compression, request policies.
- Route 53 routes
A – ALIAS
andAAAA – ALIAS
records to the CloudFront distribution.
Step 1 – Create and Harden the S3 Bucket
1.1 Name & Region
bashCopyaws s3api create-bucket \
--bucket example-com-website \
--region ap-southeast-2 \
--create-bucket-configuration LocationConstraint=ap-southeast-2
Use a globally unique bucket name and pick the Region closest to your content pipeline (not your audience—that’s CloudFront’s job).
1.2 Block Public Access at the Bucket Level
bashCopyaws s3api put-public-access-block \
--bucket example-com-website \
--public-access-block-configuration 'BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true'
1.3 Enable S3 Versioning (Optional but Recommended)
bashCopyaws s3api put-bucket-versioning \
--bucket example-com-website \
--versioning-configuration Status=Enabled
Versioning protects from fat‑finger deletes and bad deploys. Storage cost is minimal; expire old versions with lifecycle rules later.
1.4 Upload Content
bashCopyaws s3 sync ./public s3://example-com-website \
--exclude ".git/*" --cache-control "public,max-age=31536000,immutable"
Cache‑control headers matter; more on that under CloudFront policies.
Step 2 – Provision an Origin Access Control (OAC)
OAC replaces the old Origin Access Identity (OAI) and supports SigV4 signing, encryption, and header control.
bashCopyaws cloudfront create-origin-access-control \
--origin-access-control-config file://oac.json
oac.json
:
jsonCopy{
"Name": "example-oac",
"SigningProtocol": "sigv4",
"SigningBehavior": "always",
"OriginAccessControlType": "s3"
}
Note the Id
in the response; you’ll reference this when creating the distribution.
Step 3 – Issue an SSL Certificate with ACM
ACM certificates must live in us‑east‑1 for CloudFront.
bashCopyaws acm request-certificate \
--domain-name example.com \
--subject-alternative-names www.example.com \
--validation-method DNS \
--region us-east-1
Retrieve the CNAME validation record from the output and add it to Route 53. ACM auto‑renews every 13 months—no cron jobs.
Step 4 – Create the CloudFront Distribution
bashCopyaws cloudfront create-distribution --distribution-config file://dist.json
dist.json
(trimmed for brevity):
jsonCopy{
"CallerReference": "example-2025-${TIMESTAMP}",
"Origins": {
"Items": [
{
"Id": "s3Origin",
"DomainName": "example-com-website.s3.ap-southeast-2.amazonaws.com",
"S3OriginConfig": {
"OriginAccessIdentity": ""
},
"OriginAccessControlId": "E123ABC456D", // from Step 2
"OriginShield": {
"Enabled": true,
"OriginShieldRegion": "ap-southeast-2"
}
}
],
"Quantity": 1
},
"DefaultCacheBehavior": {
"TargetOriginId": "s3Origin",
"ViewerProtocolPolicy": "redirect-to-https",
"AllowedMethods": {
"Quantity": 2,
"Items": ["GET", "HEAD"]
},
"Compress": true,
"CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6", // CachingOptimized
"ResponseHeadersPolicyId":"67f7725c-6f97-4210-82d7-5512b31e9d03" // SecurityHeadersPolicy
},
"ViewerCertificate": {
"ACMCertificateArn": "arn:aws:acm:us-east-1:123:certificate/abcd-1234",
"SslSupportMethod": "sni-only",
"MinimumProtocolVersion": "TLSv1.2_2021"
},
"DefaultRootObject": "index.html",
"Enabled": true,
"HttpVersion": "http3"
}
Key settings:
- Origin Shield reduces origin fetches for multi‑region traffic; cost $0.009/GB but worth it for high read ratios.
- SecurityHeadersPolicy adds HSTS, X‑Content‑Type‑Options, and Content‑Security‑Policy.
- HTTP/3 (
HttpVersion: "http3"
) for mobile performance. - ViewerProtocolPolicy redirects HTTP to HTTPS, avoiding mixed‑content SEO penalties.
Distribution deployment takes 5‑15 minutes. Grab the DomainName
in response (e.g., d123abcd.cloudfront.net
).
Step 5 – Wire Up Route 53 DNS
Create two ALIAS records in your hosted zone:
Name | Type | Alias Target |
---|---|---|
example.com | A | d123abcd.cloudfront.net |
example.com | AAAA | d123abcd.cloudfront.net |
www.example.com | A | (same) |
www.example.com | AAAA | (same) |
ALIAS counts as in‑zone—no additional cost—and supports IPv6.
DNS TTL 60 seconds is typical; CloudFront already caches heavily.
Step 6 – CI/CD: GitHub Actions Deployment Snippet
.github/workflows/deploy.yml
yamlCopyname: S3 Deploy
on:
push:
branches: [main]
permissions:
id-token: write
contents: read
env:
BUCKET: example-com-website
REGION: ap-southeast-2
jobs:
sync:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Build static site
run: npm ci && npm run build # adapt to your generator
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v3
with:
role-to-assume: arn:aws:iam::123:role/GitHubOIDCDeploy
aws-region: ${{ env.REGION }}
- name: Sync to S3
run: |
aws s3 sync ./dist s3://$BUCKET \
--delete \
--cache-control 'public,max-age=31536000,immutable'
- name: Invalidate CloudFront
run: aws cloudfront create-invalidation \
--distribution-id E1XY2Z3ABC \
--paths '/*'
OIDC role means no static secrets in GitHub; role trust policy references your repo slug.
Step 7 – Performance & Cost Tuning
Cache‑Control Strategy
- Immutable assets (
app.[hash].js
, images) –public,max-age=31536000,immutable
- HTML entry point –
max-age=0,no-cache
to refresh quickly; rely on build hash invalidations.
CloudFront Functions vs Lambda@Edge
Need URL rewrites (/blog/hello
→ /blog/hello/index.html
)? CloudFront Functions (JavaScript V8, 1 ms limit) run cheaper and faster than Lambda@Edge. Example:
jsCopyfunction handler(event) {
const r = event.request
if (r.uri.endsWith('/')) r.uri += 'index.html'
return r
}
Attach to viewer-request
.
Monitoring
- S3 Storage Lens – Track total GB and delete unused versions monthly.
- CloudFront Real‑Time Logs – Sample 1 % to Kinesis for latency and cache‑miss analytics.
- AWS WAF – Basic Bot Control now $0.50 per million requests; consider if scraping spikes.
Monthly Bill Rough‑Cut
Item | Baseline |
---|---|
S3 storage (5 GB) | $0.12 |
S3 PUT/COPY (1 k) | $0.005 |
S3 GET (5 M) | $0.02 |
CloudFront egress (50 GB) | $0.35 |
Route 53 Hosted Zone | $0.50 |
Total ≈ | $1.00 – $2.00 |
Even a viral post (500 GB egress) lands at ~$7.
Two Keyword‑Rich Headings

How to Host a Secure Static Website on AWS S3 with CloudFront and Route 53 in 2025—Troubleshooting Common Errors
403 Forbidden at CloudFront Edge
Check: Did you attach the OAC ID and update the bucket policy with the CloudFrontOriginAccessIdentity
principal? If using OAC, the console auto‑creates the policy; CLI users often forget.
SSL Pending Validation
Check: Route 53 record set wrong type (CNAME not ALIAS)? ACM needs exact CNAME values. Propagation can take 30 minutes.
Redirect Loops
Check: You added a website endpoint policy in S3. Remove static website hosting; CloudFront handles redirects.
Missing index.html
on Deep Links
Fix: Add CloudFront Function rewrite or enable SPA fallback via CustomErrorResponses
mapping 404 → /index.html
(size small < 512 bytes).
How to Host a Secure Static Website on AWS S3 with CloudFront and Route 53 in 2025—Future‑Proofing for Growth
- Image CDN – Add CloudFront Functions that redirect
/_img/*
to CloudFront Image Optimization (Thumbor‑style) for automatic WebP/AVIF conversions. - Edge SSR – For occasional dynamic content, stitch Lambda@Edge or CloudFront Functions to fetch API data and inject into HTML fragments.
- Multi‑Region Resilience – Replicate S3 bucket with Cross‑Region Replication (CRR) to us‑west‑2; failover CloudFront origin during Region outages.
- Security Headers – CSP, Permissions‑Policy (
interest‑cohort=()
) andCross‑Origin-Opener-Policy
can all be set via Response Headers Policy.
FAQ
Does CloudFront support HTTP/3 for custom domains yet?
Yes, available GA since 2024. Set HttpVersion
to http3
in the distribution config; browsers gracefully fall back to HTTP/2.
Can I keep S3 static website hosting enabled and still use CloudFront?
Technically yes, but you lose HTTPS directly to S3 and expose public bucket endpoints. Private bucket + OAC is safer.
How do I handle contact‑form submissions on a static site?
Use API Gateway + Lambda or AWS SES’s new Form‑Post feature to receive emails without running servers.
Is CloudFront’s free tier still 1 TB?
Yes—first 1 TB egress each month is free for 12 months on new accounts; thereafter standard rates apply.
How long does ACM take to issue certificates?
With DNS validation in Route 53, often under 15 minutes; email validation can take hours.