Skip to main content

Host a Static Site on AWS for $2/Month: Complete S3 Guide 2026

November 10, 2025

Complete guide to hosting static websites on AWS S3 + CloudFront for ~$2/month. Includes security setup, deployment automation, and cost optimization. Real-world architecture.

Host a Static Site on AWS for $2/Month: Complete S3 Guide 2026

This site runs on AWS. S3 for storage, CloudFront for distribution. Fast, secure, costs about $2-3 per month.

What you’ll get:

  • Static website hosting for ~$2/month
  • Global CDN with CloudFront (sub-100ms latency)
  • Free SSL with auto-renewal
  • Custom domain support
  • Security headers (CSP, HSTS, etc.)

What you need:

  • AWS account
  • Domain name
  • Static site generator (Hugo, Jekyll, Next.js, etc.)
  • 1-2 hours for initial setup

Tech stack: S3 (storage) + CloudFront (CDN) + Route 53 (DNS) + ACM (SSL)

Quick Navigation: Setup Guide | Security | Deployment | Costs | Troubleshooting

Why Static Sites?

I’ve run WordPress sites, custom CMSs, application servers. For content sites, static hosting is better.

No server-side code means no vulnerabilities to patch. No database means no SQL injection. Pre-built HTML from a CDN is fast - no queries, no rendering, just files from the nearest edge location.

S3 storage is pennies. CloudFront bandwidth is minimal. Compare that to $20-50/month for managed WordPress or running EC2 24/7.

Static Hosting: Cost Comparison

PlatformMonthly CostSetup TimeCustom DomainSSLAuto-Deploy
AWS S3 + CloudFront$2-31-2 hours✅ Free✅ Free (ACM)Manual/scripted
Netlify$0-1915 mins✅ Free✅ Free✅ Built-in
Vercel$0-2015 mins✅ Free✅ Free✅ Built-in
GitHub PagesFree30 mins✅ Free✅ Free✅ Built-in
Traditional VPS$5-202-4 hours✅ Extra cost✅ Manual❌ Manual
Managed WordPress$20-505 mins✅ Included✅ Included❌ None

Why AWS when free options exist?

  • Full control over infrastructure
  • No vendor lock-in
  • Enterprise-grade reliability
  • Scales to millions of requests
  • Learn AWS (career benefit)
  • Security headers customization

The Setup

Static Site AWS Architecture

S3 stores the files. CloudFront serves them globally with caching and SSL. Route 53 handles DNS. ACM provides free SSL certificates.

S3: Website Hosting

Enable S3 static website hosting. Bucket is public with read-only access. CloudFront uses the S3 website endpoint.

This is the correct setup for static websites. S3 website hosting was designed for this exact use case.

Why this approach:

  • Directory index support - /tags/msp/tags/msp/index.html automatically
  • Custom error pages - 404 handling works natively
  • RFC 3986 compliant - Proper URI handling for web content
  • Simple configuration - No Lambda@Edge or complex routing needed
  • HTTPS via CloudFront - Secure delivery even with public S3 bucket

The bucket is public (read-only), but that’s fine. You’re hosting a public website. The files are meant to be public. CloudFront adds HTTPS, caching, and security headers on top.

CloudFront: Global Distribution

CloudFront sits in front of S3. Visitors hit an edge location near them. Content gets cached at edge locations. Most requests never touch S3.

CloudFront handles HTTPS, custom domains, compression, security headers. Redirect HTTP to HTTPS. Compress automatically. Custom error pages.

Use Response Headers Policy for security - CSP, HSTS, X-Frame-Options. Protects against vulnerabilities without application code.

Route 53 and ACM

Point your domain to CloudFront with alias records. Free query charges. Works for apex domains and subdomains.

ACM provides free SSL with auto-renewal. Request certificate, validate via DNS, attach to CloudFront. Certificate must be in us-east-1.

Step-by-Step Setup Guide

Total time: 1-2 hours Skill level: Intermediate (comfortable with AWS Console and CLI)

Step 1: Create S3 Bucket (10 mins)

  1. Log into AWS Console → S3
  2. Create bucket: your-domain.com
  3. Uncheck “Block all public access” (we need public read for website hosting)
  4. Enable Static Website Hosting:
    • Index document: index.html
    • Error document: 404.html
  5. Add bucket policy for public read access:
{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "PublicReadGetObject",
    "Effect": "Allow",
    "Principal": "*",
    "Action": "s3:GetObject",
    "Resource": "arn:aws:s3:::your-domain.com/*"
  }]
}
  1. Note the S3 website endpoint: http://your-domain.com.s3-website-us-east-1.amazonaws.com

Step 2: Request SSL Certificate (5 mins)

  1. AWS Console → Certificate Manager
  2. Region: us-east-1 (CloudFront requires this)
  3. Request public certificate for:
    • your-domain.com
    • *.your-domain.com (wildcard for subdomains)
  4. Validation method: DNS
  5. Add CNAME records to your DNS (or Route 53)
  6. Wait 5-30 mins for validation

Step 3: Create CloudFront Distribution (15 mins)

  1. AWS Console → CloudFront → Create Distribution
  2. Origin Domain: Paste your S3 website endpoint (NOT the dropdown S3 bucket)
    • Example: your-domain.com.s3-website-us-east-1.amazonaws.com
  3. Viewer Protocol Policy: Redirect HTTP to HTTPS
  4. Allowed HTTP Methods: GET, HEAD, OPTIONS
  5. Cache Policy: CachingOptimized
  6. Alternate domain names (CNAMEs): your-domain.com, www.your-domain.com
  7. Custom SSL certificate: Select your ACM certificate
  8. Default root object: index.html
  9. Response headers policy: Create custom policy (see security section)
  10. Create distribution
  11. Note CloudFront distribution ID and domain name

Step 4: Configure DNS (5 mins)

  1. AWS Console → Route 53 (or your DNS provider)
  2. Create A record for apex domain:
    • Name: your-domain.com
    • Type: A - IPv4 address
    • Alias: Yes
    • Target: Your CloudFront distribution
  3. Create A record for www:
    • Name: www.your-domain.com
    • Type: A - IPv4 address
    • Alias: Yes
    • Target: Your CloudFront distribution

Step 5: Upload Your Site (10 mins)

# Build your static site (example: Hugo)
hugo --cleanDestinationDir

# Sync to S3
aws s3 sync ./public s3://your-domain.com/ --delete

# Invalidate CloudFront cache
aws cloudfront create-invalidation \
  --distribution-id YOUR_DIST_ID \
  --paths "/*"

Step 6: Test

  1. Visit https://your-domain.com
  2. Check SSL certificate (should show valid)
  3. Test HTTP → HTTPS redirect
  4. Check security headers: curl -I https://your-domain.com
  5. Test from multiple geographic locations

Total setup time: 1-2 hours first time, 15 minutes for subsequent sites

Security

Static sites are more secure. Configuration still matters.

Content Security Policy (CSP) restricts what loads. Configure it in CloudFront Response Headers Policy. Default-deny everything, then explicitly allow trusted sources. Scripts from self and CDNs only. No inline scripts, no eval().

Example CSP configuration:

default-src 'none';
script-src 'self' https://cdnjs.cloudflare.com;
style-src 'self' https://cdnjs.cloudflare.com;
img-src 'self' data:;
font-src 'self' data:;
connect-src 'self';
frame-ancestors 'none';
base-uri 'self';
form-action 'self';

Add HSTS to force HTTPS. X-Content-Type-Options to prevent MIME sniffing. X-Frame-Options to block framing. CloudFront Response Headers Policy handles all of this.

Enable S3 versioning for rollback capability. For public S3 website hosting, bucket policy allows public read access (files are public anyway - it’s a website). Access logging for audit trails.

Deploy Process

I use Hugo. Works with Jekyll, Gatsby, Next.js, 11ty, whatever.

  1. Build (hugo --cleanDestinationDir)
  2. Sync to S3 (aws s3 sync)
  3. Invalidate CloudFront (aws cloudfront create-invalidation)

Takes 30 seconds. Live globally in minutes. First 1,000 invalidations free monthly.

What It Costs

Real numbers:

  • S3 Storage: $0.20/month for 10GB
  • S3 Requests: $0.10/month
  • CloudFront: $1.00/month for moderate traffic
  • Route 53: $0.50/month
  • ACM: Free

Total: ~$2/month

WAF adds $5-10/month for bot protection. Overkill for most blogs.

Cost Scaling Warning

These costs are based on a low-traffic content site (~100k requests/month). CloudFront costs scale with bandwidth and request volume. High-traffic sites can cost significantly more. Calculate your expected costs before deploying. See Terms of Service for important disclaimers.

Common Issues & Solutions

Problem: “Access Denied” on S3 bucket

Cause: Bucket policy doesn’t allow public read

Solution:

# Verify bucket policy allows s3:GetObject
aws s3api get-bucket-policy --bucket your-domain.com

Ensure policy includes:

"Action": "s3:GetObject",
"Principal": "*"

Problem: CloudFront serves old cached content

Cause: CloudFront edge caches have 24-hour TTL by default

Solution:

# Invalidate cache
aws cloudfront create-invalidation \
  --distribution-id YOUR_DIST_ID \
  --paths "/*"

# Or for specific files
aws cloudfront create-invalidation \
  --distribution-id YOUR_DIST_ID \
  --paths "/index.html" "/about/index.html"

Problem: Certificate validation stuck

Cause: DNS CNAME records not added correctly

Solution:

  1. Check ACM console for validation status
  2. Copy CNAME name and value exactly
  3. Add to DNS (may take 30 mins to propagate)
  4. Check DNS: dig _validation.your-domain.com CNAME

Problem: Subdirectory URLs return 404

Cause: CloudFront origin not using S3 website endpoint

Solution: Ensure origin domain is:

  • your-bucket.s3-website-region.amazonaws.com (S3 website endpoint)
  • your-bucket.s3.region.amazonaws.com (S3 REST API endpoint)

S3 website endpoint automatically serves /path//path/index.html

Problem: High CloudFront costs

Cause: Excessive invalidations or high traffic

Solution:

  • Use cache-busting filenames for assets: style.abc123.css
  • Invalidate only changed files, not /*
  • Monitor CloudFront metrics for unusual traffic
  • Enable CloudFront access logging to identify issues

When This Doesn’t Work

Need dynamic infrastructure for:

  • User-generated content
  • Real-time features
  • Complex forms
  • Personalized content per user

You’ll need application servers and databases. But for blogs, documentation, portfolios? Static wins.

Cloud Reliability Reality

AWS markets 99.999999999% durability. No infrastructure is perfect.

The November 2025 AWS outage reminded everyone. Services down across regions. When your cloud provider has an outage, you’re down. You wait.

That’s the trade-off. You get AWS’s engineering and redundancy. When AWS has a bad day, you have a bad day.

For a content site, makes sense. Outage lasted hours, not days. AWS track record is solid. Multi-cloud redundancy costs exponentially more.

If you have SLA commitments or revenue tied to uptime, multi-region failover makes sense. For a blog? Accept occasional outages.

The cloud is someone else’s computers. They break sometimes.

Why I Use This

I’ve built complex infrastructures. Load balancers, auto-scaling, RDS, microservices. Necessary for the right workloads.

For this site? No maintenance. No patches, no updates, no server management. Security by design. Fast delivery. Pay for usage, not idle compute.

Fast, secure, cheap. Zero time on infrastructure. I write content, not manage servers.

Getting Started

Setup takes an hour or two.

Choose a static site generator. Create S3 bucket with website hosting enabled. Setup CloudFront pointing to S3 website endpoint. Configure Route 53. Add ACM certificate. Security headers. Deployment script.

Then just write and deploy. No patching, no updates, no backups. Infrastructure runs itself.