Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

AWS S3 Static Website Hosting with CloudFront CDN

By Kokil Thapa | Last reviewed: August 2026

AWS S3 static website hosting with CloudFront CDN is the industry-standard architecture for serving secure, low-latency content without managing servers. While S3 provides durable object storage, exposing buckets directly via the legacy website endpoint creates security risks and lacks HTTPS support at the edge. For any production deployment in 2026, you must front your bucket with CloudFront using Origin Access Control (OAC) to enforce private access while delivering cached assets globally.

I have deployed this exact pattern for legal-tech portals and documentation sites where uptime and security are non-negotiable. Unlike traditional shared hosting or VPS setups discussed in AWS cloud hosting comparisons, this serverless approach eliminates OS patching and PHP-FPM tuning entirely. The trade-off is initial configuration complexity, but once established, it scales indefinitely with near-zero marginal cost for read-heavy workloads.

Why use AWS S3 static website hosting with CloudFront CDN instead of direct S3?

Direct S3 website hosting is effectively deprecated for production use. It supports only HTTP, exposes your bucket name publicly, and offers no DDoS protection or edge caching. CloudFront solves these deficiencies by acting as a secure reverse proxy layer between users and your origin.

User BrowserHTTPS RequestCloudFront + WAFEdge CacheSSL TerminationGeo Restriction✓ OAC Signed RequestsPrivate S3 BucketOrigin (No Public)ACM Certificate
Secure architecture for AWS S3 static website hosting with CloudFront CDN using private origin and signed requests

The critical distinction in 2026 is Origin Access Control (OAC). Previously, developers used Origin Access Identity (OAI), which required complex bucket policies referencing specific CloudFront IDs. OAC simplifies this by allowing CloudFront to sign requests natively. Your bucket policy now simply permits cloudfront.amazonaws.com as a service principal, making infrastructure-as-code significantly cleaner and more portable across environments.

Performance gains are substantial. On a recent legal information portal I maintained, moving from direct S3 to CloudFront reduced Time-to-First-Byte (TTFB) from ~400ms to under 50ms for cached assets in South Asia. For Nepal-based users, the Mumbai and Kolkata edge locations provide excellent latency without requiring local infrastructure investment.

How do you configure S3 and CloudFront securely with OAC?

Security misconfiguration is the most common failure mode. Never enable "Static website hosting" on the S3 bucket when using CloudFront; that feature is for direct HTTP access only. Instead, keep the bucket private and let CloudFront handle all public-facing concerns.

Step 1: Create the private S3 bucket

Block all public access at the account level via S3 Block Public Access settings, then create your bucket with default encryption enabled. Upload your built static assets (HTML, CSS, JS, images). Ensure your build process generates cache-busted filenames (e.g., app.a1b2c3d4.js) so CloudFront can safely cache immutable assets forever.

<!-- Example bucket policy allowing ONLY CloudFront OAC -->
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowCloudFrontServicePrincipalReadOnly",
            "Effect": "Allow",
            "Principal": {
                "Service": "cloudfront.amazonaws.com"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::my-static-site-bucket/*",
            "Condition": {
                "StringEquals": {
                    "AWS:SourceArn": "arn:aws:cloudfront::123456789:distribution/EXXXXXXXXX"
                }
            }
        }
    ]
}

Step 2: Provision ACM certificate in us-east-1

CloudFront requires SSL certificates from the us-east-1 region exclusively. Request a certificate via AWS Certificate Manager covering both example.com and *.example.com. Validate via DNS (fastest) or email. This step blocks many deployments because engineers request certs in their home region (e.g., ap-south-1) and cannot select them in CloudFront.

Step 3: Configure CloudFront distribution with OAC

In the CloudFront console or Terraform/Pulumi config:

  • Origin domain: Select the S3 bucket REST endpoint (bucket.s3.region.amazonaws.com), NOT the website endpoint.
  • Origin access: Choose "Origin access control settings" → Create new OAC setting → Sign requests with SigV4.
  • Viewer protocol policy: Redirect HTTP to HTTPS.
  • Allowed HTTP methods: GET, HEAD, OPTIONS (never enable PUT/DELETE for static sites).
  • Cache policy: Use "CachingOptimized" for assets; create custom policy for HTML with shorter TTL.
  • Custom SSL certificate: Select your us-east-1 ACM cert.
  • Default root object: Set to index.html.

After creation, CloudFront displays a "Copy bucket policy" button. Apply this exact policy to your S3 bucket. This completes the OAC trust chain.

How do you handle SPA routing and error pages correctly?

Single-page applications (React, Vue, Laravel Livewire hybrid) break on CloudFront because requesting /about returns a 403/404 from S3 — there is no physical /about object. You must configure custom error responses to rewrite these to /index.html while preserving the browser URL.

GET /dashboardBrowser RequestS3 Returns 403No Object FoundCloudFront Error ResponseCustom 403 → /index.htmlHTTP Status: 200 OKCache TTL: 0 (no cache)SPA Router Takes OverClient-Side Navigation
CloudFront error response configuration enabling client-side routing for SPAs on AWS S3 static website hosting with CloudFront CDN

Configure two custom error responses in CloudFront:

  1. 403 Forbidden: Response page = /index.html, HTTP response code = 200, Cache TTL = 0.
  2. 404 Not Found: Same configuration as above.

The zero-second TTL is critical. Without it, CloudFront caches the index.html response for errors, causing subsequent legitimate 404s to return stale content. For true 404 pages (missing images, broken links), handle those in your application router after hydration. This pattern works identically for Vue Router, React Router, and Alpine.js-based navigation systems I've implemented in Laravel Livewire projects.

For multi-page static sites (Hugo, Jekyll, plain HTML), skip this entirely. Each route has a physical file, and CloudFront serves them directly. Only SPAs need the error-response rewrite trick.

What is the optimal caching strategy for performance and freshness?

Blindly caching everything causes stale content disasters. A tiered cache policy separates immutable assets from mutable documents.

Asset TypeCache-Control HeaderCloudFront TTLRationale
JS/CSS/Images (hashed)public, max-age=31536000, immutable1 yearFilename changes on rebuild; safe to cache forever
Fonts (woff2)public, max-age=31536000, immutable1 yearRarely change; critical render path
HTML documentspublic, max-age=0, must-revalidate0 secondsAlways check origin for updates; prevents stale routes
JSON/API manifestspublic, max-age=601 minuteShort grace period for deployment propagation
robots.txt / sitemap.xmlpublic, max-age=36001 hourCrawlers tolerate slight staleness

Set these headers during your build/upload step, not in CloudFront behaviors. CloudFront respects origin headers by default when using managed cache policies. If you need to override, create a custom cache policy rather than modifying behavior settings directly — this keeps configurations reusable across distributions.

For invalidation after deploys, avoid wildcard /* invalidations on large sites; they're expensive ($0.005 per path after first 1,000) and slow. Instead, invalidate only /index.html and any changed HTML routes. Hashed assets never need invalidation because their URLs change. This approach keeps monthly costs predictable even with frequent deployments.

How does AWS S3 static website hosting with CloudFront CDN compare to alternatives?

Choosing the right platform depends on team expertise, budget, and operational tolerance. I've used all three patterns below for different client scenarios.

AWS S3 + CloudFront✓ Full control & lowest scale cost✓ OAC security model✓ Integrates with AWS ecosystem✗ High setup complexity✗ Manual SSL renewal trackingBest: Enterprise / High TrafficCost: ~$1-3/mo base + transferVercel / Netlify✓ Zero-config deploys✓ Automatic preview branches✓ Built-in form/auth functions✗ Vendor lock-in risk✗ Expensive at high bandwidthBest: Startups / Rapid IterationCost: Free tier → $20+/mo proTraditional VPS✓ Full server control✓ Run dynamic apps alongside✓ Predictable flat pricing✗ OS/security maintenance✗ No automatic global CDNBest: Hybrid Dynamic+StaticCost: $5-20/mo fixed
Decision framework comparing AWS S3 static website hosting with CloudFront CDN against managed platforms and VPS options

For Nepal-based businesses targeting local audiences, consider bandwidth costs carefully. AWS charges ~$0.085/GB from Mumbai/Kolkata edges to internet. A site serving 500GB/month costs ~Rs 5,500 (~$42) in transfer alone. Vercel's free tier includes 100GB; beyond that, Pro plans start at $20/month with 1TB included. For low-traffic brochure sites (50GB/month), AWS wins on absolute cost. For medium-traffic sites with unpredictable spikes, managed platforms cap financial risk.

Operational overhead matters too. With AWS, you own SSL renewal monitoring, WAF rule updates, and cache invalidation scripting. Managed platforms abstract this entirely. I recommend AWS S3 static website hosting with CloudFront CDN primarily when you already operate within AWS (RDS, Lambda, SES) and want unified billing/IAM, or when traffic volume justifies the engineering investment. For standalone marketing sites or portfolios, Vercel or Netlify deliver faster time-to-value.

Deploying and maintaining your static site reliably

Manual uploads via AWS Console are unacceptable for production. Automate deployments through CI/CD pipelines that build, test, and sync assets atomically. I use GitLab CI with the AWS CLI for most projects, following patterns similar to those in my CI/CD pipeline guides.

# .gitlab-ci.yml snippet for S3 + CloudFront deploy
deploy:
  stage: deploy
  image: amazon/aws-cli:latest
  script:
    - aws s3 sync dist/ s3://$BUCKET_NAME --delete --cache-control "public,max-age=31536000,immutable" --exclude "*.html" --exclude "sitemap.xml" --exclude "robots.txt"
    - aws s3 sync dist/ s3://$BUCKET_NAME --delete --cache-control "public,max-age=0,must-revalidate" --include "*.html" --include "sitemap.xml" --include "robots.txt"
    - aws cloudfront create-invalidation --distribution-id $CF_DIST_ID --paths "/index.html" "/sitemap.xml" "/robots.txt"
  only:
    - main

The dual-sync strategy applies correct cache headers per file type without relying on S3 metadata overrides. The --delete flag removes orphaned files from previous builds, preventing bloat and potential security exposure of old assets. Invalidation targets only mutable entry points; hashed assets self-version.

Monitor actively. Enable CloudFront real-time logs to S3 or Kinesis for debugging cache misses and 4xx spikes. Set up CloudWatch alarms on 5xx error rates (>1% threshold) and origin latency (>500ms p95). These catch misconfigurations before users report issues. For cost governance, create AWS Budgets alerts at 50%/80%/100% of expected monthly spend — unexpected traffic surges or cache misconfigs can inflate bills overnight.

Finally, document your infrastructure. Store Terraform/Pulumi state remotely, version-control bucket policies, and maintain runbooks for certificate renewal and disaster recovery. Infrastructure-as-code isn't optional for AWS S3 static website hosting with CloudFront CDN; it's what separates maintainable systems from fragile ones that break when the original engineer leaves.

Moving forward with confidence

AWS S3 static website hosting with CloudFront CDN delivers enterprise-grade performance and security when configured correctly with OAC, proper caching tiers, and automated deployments. The initial setup demands precision, but the result is a resilient, scalable foundation that handles everything from personal portfolios to high-traffic legal portals without server management overhead.

If you're evaluating this architecture for a Nepal-based project or need help migrating an existing site to this pattern, reach out to discuss your specific requirements. I regularly help teams implement secure, cost-effective static hosting that aligns with their operational capacity and growth trajectory.

Frequently Asked Questions

For most low-traffic sites, expect Rs 50 to Rs 200 (under USD 1.50) monthly. AWS Free Tier covers 5GB storage and 1TB transfer for the first year, making it nearly free for new projects.

Use OAC instead of public read. Create an OAC in CloudFront, attach it to your distribution, then add the generated S3 bucket policy allowing only that specific CloudFront distribution ID to execute s3:GetObject actions.

S3 website endpoints lack HTTPS, caching, and global edge delivery. CloudFront provides SSL via ACM, reduces latency through edge caching, lowers egress costs, and enables WAF protection, which raw S3 hosting cannot offer.

Configure a Viewer Protocol Policy of "Redirect HTTP to HTTPS" in your CloudFront distribution behavior settings. Do not rely on S3 website redirect rules, as they only work over HTTP and break when using OAC private buckets.

Yes. Request a public certificate in AWS Certificate Manager (us-east-1 region only), validate ownership via DNS, then select it in the CloudFront distribution settings. Add CNAMEs pointing your domain to the CloudFront domain name.

Create a CloudFront Function or Lambda@Edge that intercepts 403/404 errors from S3 and returns index.html with a 200 status code. This ensures client-side routes resolve correctly without exposing error pages to users or crawlers.

Version your assets using content hashes in filenames so they get unique URLs. Only invalidate /index.html on deploy since it references hashed assets. This avoids expensive wildcard invalidations while ensuring users always receive fresh content immediately.

S3 plus CloudFront offers finer control and lower costs at scale but requires manual CI/CD setup. Netlify and Vercel provide built-in preview deploys and form handling. I prefer S3 for Nepal clients needing data sovereignty or existing AWS infrastructure.

Enable CloudFront signed URLs or signed cookies for private content. For public sites, use WAF rules to block unwanted referrers or geographies. Never make the S3 bucket public; restrict access exclusively through CloudFront OAC policies.

S3 serves files based on Content-Type metadata, not file extension. If CSS or JS loads as text/plain, browsers reject them. Set correct Content-Type during upload via CLI flag --content-type or SDK metadata parameter to ensure proper rendering.

Use aws cli s3 sync in your pipeline job to upload build artifacts, then call create-invalidation for index.html. Store AWS credentials as masked CI variables. On projects I maintain, this runs automatically after merge to main branch.

Yes, if you enable compression in CloudFront cache behavior settings. CloudFront compresses eligible text-based responses on the fly when origin objects lack pre-compressed versions. Pre-compressing large assets in S3 can further reduce origin fetch time.

Verify OAC is attached correctly and bucket policy references the exact distribution ARN. Check that S3 Block Public Access is enabled but allows OAC. Confirm index.html exists at root. Review CloudFront logs to distinguish origin vs viewer errors.

Set default TTL to 86400 seconds for immutable hashed assets. Use Cache-Control max-age=0, must-revalidate for index.html to ensure HTML updates propagate instantly. Override these in CloudFront behaviors rather than relying solely on S3 object metadata headers.

Yes, for static content. I have deployed legal-tech portals and directory sites serving thousands of daily visitors using this stack. Costs remain predictable under NPR 500 monthly even during peak seasons like Dashain, unlike traditional shared hosting bandwidth overages.

Share this article

Quick Contact Options
Choose how you want to connect me: