AWS S3 Static Website Deployment Guide: Complete Setup with CloudFront and Custom Domain Production Ready

Learn how to deploy a production-ready static website on AWS S3 with CloudFront CDN, SSL certificates, and custom domain setup. Complete step-by-step guide with troubleshooting.

AWS S3 Static Website Deployment Guide: Complete Setup with CloudFront and Custom Domain Production Ready

Table of Contents

AWS S3 Static Website Deployment Guide: Complete Setup with CloudFront and Custom Domain

Introduction

Deploying a static website on AWS S3 with CloudFront CDN provides a cost-effective, scalable, and secure solution for hosting static content. This comprehensive guide walks you through setting up a production-ready static website with SSL certificates, custom domain configuration, and global content delivery.

What You’ll Learn

  • How to create and configure an S3 bucket for static website hosting
  • Setting up SSL certificates with AWS Certificate Manager (ACM)
  • Configuring CloudFront CDN for global content delivery
  • Setting up custom domains with Route 53 and Cloudflare
  • Security best practices and cost optimization
  • Troubleshooting common deployment issues

Prerequisites

  • AWS Account with administrative access
  • Domain registered with a DNS provider (e.g., Namecheap, GoDaddy)
  • Cloudflare account with DNS management access
  • Website files ready for deployment
  • Basic understanding of AWS services (S3, CloudFront, Route 53)
  • Familiarity with DNS concepts and domain management

Architecture Overview

Our deployment architecture follows this flow:

User Request → Cloudflare DNS → Route 53 → CloudFront → Private S3 Bucket

This setup provides:

  • Global CDN: CloudFront edge locations worldwide
  • Security: Private S3 bucket with Origin Access Control (OAC)
  • SSL/TLS: Free ACM certificates with automatic renewal
  • Custom Domain: Professional domain setup with DNS delegation

Step-by-Step Deployment

Phase 1: Create S3 Bucket

1.1 Navigate to S3 Console

  1. Go to AWS S3 Console
  2. Click “Create bucket”

1.2 Configure Bucket Settings

Essential Configuration:

  • Bucket name: static.yourdomain.com (must be globally unique)
  • AWS Region: Choose closest to your audience (e.g., us-east-1)
  • Object Ownership: ACLs disabled (recommended for security)
  • Block Public Access: Keep all options checked (we’ll use CloudFront for access)
  • Bucket Versioning: Disable (unless you need file versioning)
  • Default encryption: Amazon S3 managed keys (SSE-S3)

1.3 Upload Website Files

Method 1: AWS Console

  1. Open your newly created bucket
  2. Click “Upload”“Add files”
  3. Select your website files (index.html, CSS, JS, images)
  4. Click “Upload”

Method 2: AWS CLI

# Navigate to your project directory
cd your-website-directory

# Upload files to S3
aws s3 sync ./ s3://static.yourdomain.com/ --exclude ".*"

Phase 2: SSL Certificate Setup (ACM)

2.1 Request Certificate

  1. Go to AWS Certificate Manager
  2. CRITICAL: Switch region to US East (N. Virginia) us-east-1
    • CloudFront requires certificates in this region only
  3. Click “Request certificate”
  4. Select “Request a public certificate” → Click “Next”

2.2 Configure Domain Names

  1. Domain names:
    • Add: static.yourdomain.com
    • Optionally add: *.static.yourdomain.com for future subdomains
  2. Validation method: Select “DNS validation”
  3. Key algorithm: RSA 2048 (default)
  4. Click “Request”

2.3 Get Validation Records

  1. Click on the certificate ID to view details
  2. In the “Domains” section, copy the CNAME records needed for validation
  3. Example CNAME record:
    • Name: _abc123def456.static.yourdomain.com
    • Value: _xyz789ghi012.acm-validations.aws.

Phase 3: Route 53 Hosted Zone Setup

3.1 Create Hosted Zone

  1. Go to Route 53 Console
  2. Click “Hosted zones”“Create hosted zone”
  3. Domain name: static.yourdomain.com
  4. Type: Public hosted zone
  5. Click “Create hosted zone”

3.2 Note Nameservers

After creation, you’ll see an NS record with 4 nameservers:

ns-123.awsdns-45.com
ns-678.awsdns-90.net
ns-1234.awsdns-56.org
ns-5678.awsdns-78.co.uk

Copy all 4 nameservers - you’ll add these to Cloudflare.

3.3 Add ACM Validation Records

  1. In your Route 53 hosted zone, click “Create record”
  2. Record name: Paste the CNAME name from Phase 2.3 (remove domain suffix)
  3. Record type: CNAME
  4. Value: Paste the CNAME value from Phase 2.3
  5. TTL: 300 seconds
  6. Click “Create records”

3.4 Wait for Certificate Validation

  • Certificate status should change from “Pending validation” to “Issued”
  • This typically takes 5-30 minutes
  • Do not proceed to CloudFront until certificate is issued

Phase 4: Cloudflare DNS Delegation

4.1 Access Cloudflare Dashboard

  1. Go to Cloudflare Dashboard
  2. Select your domain
  3. Go to “DNS”“Records”

4.2 Create NS Records

  1. Click “Add record”
  2. Type: NS
  3. Name: static (creates static.yourdomain.com)
  4. Nameserver: Paste the FIRST Route 53 nameserver
  5. TTL: Auto
  6. Proxy status: DNS only (gray cloud) - DO NOT enable proxy
  7. Click “Save”
  8. Repeat for the remaining 3 Route 53 nameservers

Result: You should have 4 NS records in Cloudflare pointing to Route 53 nameservers.

4.3 Verify Delegation

Wait 5-10 minutes, then verify:

nslookup -type=NS static.yourdomain.com

You should see the 4 Route 53 nameservers in the response.

Phase 5: CloudFront Distribution Setup

5.1 Create Distribution

  1. Go to CloudFront Console
  2. Click “Create distribution”

5.2 Origin Settings

  1. Origin domain: Select your S3 bucket static.yourdomain.com
  2. Origin path: Leave blank
  3. S3 bucket access:
    • Select “Yes use OAC (bucket can restrict access to only CloudFront)”
    • Click “Create control setting”
    • Name: static-domain-oac
    • Click “Create”
  4. Copy the generated policy - you’ll need this for S3 bucket policy

5.3 Default Cache Behavior Settings

  1. Viewer protocol policy: Select “Redirect HTTP to HTTPS”
  2. Allowed HTTP methods: GET, HEAD (default)
  3. Cache policy: CachingOptimized (default)
  4. Leave other settings as default

5.4 Settings

  1. Alternate domain name (CNAME):
    • Add: static.yourdomain.com
  2. Custom SSL certificate: Select your ACM certificate
  3. Default root object: Enter index.html
  4. Price class: Use all edge locations (best performance)
  5. Click “Create distribution”

5.5 Wait for Deployment

  • Status will show “Deploying” (takes 5-15 minutes)
  • Note the Distribution domain name (e.g., d1234abcd.cloudfront.net)
  • Wait until status changes to “Enabled”

Phase 6: Update S3 Bucket Policy

6.1 Apply CloudFront Policy

  1. Go to S3 Console → Your bucket
  2. Click “Permissions” tab
  3. Scroll to “Bucket policy” section → Click “Edit”
  4. Paste the policy you copied in Phase 5.2
  5. The policy should look like:
{
  "Version": "2008-10-17",
  "Id": "PolicyForCloudFrontPrivateContent",
  "Statement": [
    {
      "Sid": "AllowCloudFrontServicePrincipal",
      "Effect": "Allow",
      "Principal": {
        "Service": "cloudfront.amazonaws.com"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::static.yourdomain.com/*",
      "Condition": {
        "StringEquals": {
          "AWS:SourceArn": "arn:aws:cloudfront::YOUR_ACCOUNT_ID:distribution/YOUR_DISTRIBUTION_ID"
        }
      }
    }
  ]
}
  1. Click “Save changes”

Phase 7: Route 53 DNS Records for CloudFront

7.1 Create A Record

  1. Go to Route 53 → Hosted zones
  2. Select your hosted zone
  3. Click “Create record”
  4. Record name: Leave BLANK (creates record for static.yourdomain.com)
  5. Record type: A
  6. Alias: Toggle ON
  7. Route traffic to:
    • Select “Alias to CloudFront distribution”
    • Select your CloudFront distribution
  8. Click “Create records”

Phase 8: Testing and Verification

8.1 Test CloudFront Distribution Directly

  1. Copy your CloudFront distribution domain name
  2. Open in browser: https://d1234abcd.cloudfront.net
  3. Verify your website loads correctly

8.2 Test Custom Domain

Wait 5-10 minutes for DNS propagation, then:

  1. Open browser: https://static.yourdomain.com
  2. Verify your website loads correctly
  3. Check that HTTPS works (look for padlock icon)
  4. Test error pages: https://static.yourdomain.com/nonexistent.html

8.3 DNS Verification Commands

# Check DNS resolution
nslookup static.yourdomain.com

# Check SSL certificate
curl -I https://static.yourdomain.com

# Check CloudFront headers
curl -I https://static.yourdomain.com | grep -i cloudfront

Updating Website Content

Method 1: AWS Console

  1. Go to S3 → Your bucket
  2. Upload new/modified files (will overwrite existing)
  3. Create CloudFront invalidation:
    • Go to CloudFront → Your distribution
    • Click “Invalidations” tab → “Create invalidation”
    • Enter paths: /* (invalidates all) or /index.html (specific file)
    • Click “Create invalidation”

Method 2: AWS CLI

# Upload files
aws s3 sync ./your-website-directory/ s3://static.yourdomain.com/ --delete

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

Cost Estimation

Monthly Costs (Approximate)

  • S3 Storage: ~$0.023/GB (minimal for static sites)
  • CloudFront: Free tier includes 1TB data transfer/month
  • Route 53 Hosted Zone: $0.50/month
  • ACM Certificate: Free
  • Total: ~$0.50/month + data transfer over 1TB

Cost Optimization Tips

  1. Use CloudFront caching to reduce S3 requests
  2. Enable compression in CloudFront
  3. Use appropriate cache TTL settings
  4. Monitor usage in AWS Cost Explorer

Security Best Practices

Implemented Security Features

  1. Private S3 Bucket: No direct public access
  2. Origin Access Control (OAC): CloudFront-only access to S3
  3. HTTPS Only: Automatic HTTP to HTTPS redirect
  4. SSL/TLS Certificate: Free ACM certificate with auto-renewal
  5. DNS Security: Cloudflare DNS protection

Additional Security Recommendations

  1. Enable AWS WAF if needed for additional protection
  2. Set up CloudTrail for audit logging
  3. Use IAM roles with minimal permissions
  4. Regular security audits and monitoring
  5. Monitor access logs and set up alerts

Troubleshooting

Common Issues and Solutions

Certificate Not Validating

Problem: ACM certificate stuck in “Pending validation”

Solution:

  1. Check CNAME records in Route 53 match ACM requirements exactly
  2. Ensure no typos in domain names
  3. Wait up to 30 minutes for propagation

Website Not Loading via Custom Domain

Problem: 404 or DNS resolution errors

Solution:

  1. Verify Route 53 A record points to correct CloudFront distribution
  2. Check Cloudflare NS records are correct
  3. Wait for DNS propagation (up to 48 hours globally)

403 Forbidden Error

Problem: CloudFront returns 403 when accessing S3 content

Solution:

  1. Verify S3 bucket policy is correctly applied
  2. Check OAC is properly configured
  3. Ensure CloudFront distribution ARN matches bucket policy

Old Content Showing After Update

Problem: Changes not visible after uploading new files

Solution:

  1. Create CloudFront invalidation for updated files
  2. Check cache TTL settings
  3. Wait for cache expiration

Debugging Commands

# Check DNS resolution
dig static.yourdomain.com
nslookup static.yourdomain.com

# Check SSL certificate
openssl s_client -connect static.yourdomain.com:443 -servername static.yourdomain.com

# Check HTTP headers
curl -I https://static.yourdomain.com

# Check CloudFront distribution
aws cloudfront get-distribution --id YOUR_DISTRIBUTION_ID

Performance Optimization

CloudFront Settings

  1. Cache Behaviors: Optimize TTL settings for different content types
  2. Compression: Enable gzip compression for text files
  3. HTTP/2: Automatically enabled for better performance
  4. Edge Locations: Use all edge locations for best global performance

Website Optimization

  1. Minify CSS/JS: Reduce file sizes
  2. Optimize Images: Use WebP format when possible
  3. Lazy Loading: Implement for images and content
  4. CDN Caching: Leverage CloudFront edge locations

Monitoring and Analytics

AWS CloudWatch

  1. Monitor CloudFront metrics (requests, errors, cache hit ratio)
  2. Set up billing alerts for cost monitoring
  3. Track error rates and performance metrics

Third-Party Analytics

  1. Google Analytics for website traffic analysis
  2. Cloudflare Analytics (if using Cloudflare proxy)
  3. Custom analytics solutions

Backup and Recovery

S3 Versioning

  1. Enable versioning for important files
  2. Set up lifecycle policies for cost optimization
  3. Cross-region replication (if needed for disaster recovery)

Disaster Recovery

  1. Keep local copies of website files
  2. Document all configurations and settings
  3. Test recovery procedures regularly

Cleanup Guide

If you need to remove all resources to avoid ongoing charges, follow this comprehensive cleanup guide:

⚠️ Important Notes Before Starting

  1. Backup Important Data: Ensure you have local copies of your website files before deletion
  2. DNS Propagation: DNS changes may take up to 48 hours to propagate globally
  3. Irreversible Actions: Some deletions are permanent and cannot be undone
  4. Cost Impact: This cleanup will stop all charges related to the static website setup

Estimated Monthly Costs Being Eliminated

  • S3 Storage: ~$0.023/GB (minimal for static sites)
  • CloudFront: Free tier includes 1TB data transfer/month
  • Route 53 Hosted Zone: $0.50/month
  • ACM Certificate: Free (no charges to eliminate)
  • Total Monthly Savings: ~$0.50/month + any CloudFront overages

Cleanup Order (Critical - Follow This Sequence)

Phase 1: Delete CloudFront Distribution

  1. Navigate to CloudFront Console

    • Go to AWS CloudFront Console
    • Find your distribution
    • Status must be “Enabled” - if it shows “Deploying”, wait for it to complete
  2. Disable Distribution

    • Click on your distribution ID to open it
    • Click the “Disable” button
    • Confirm the action when prompted
    • Wait for status to change to “Disabled” (this can take 5-15 minutes)
  3. Delete Distribution

    • Once status shows “Disabled”, click the “Delete” button
    • Type the distribution ID to confirm deletion
    • Click “Delete” to confirm
    • Wait for deletion to complete

Why First: CloudFront distributions must be deleted before removing Route 53 records that point to them.

Phase 2: Delete Route 53 DNS Records

  1. Delete A Record

    • Go to Route 53 Console
    • Click “Hosted zones”
    • Select your hosted zone
    • Find the A record (should point to CloudFront distribution)
    • Click the record → “Delete” → Confirm deletion
  2. Delete ACM Validation CNAME Records

    • In the same hosted zone, find any CNAME records that start with _ (ACM validation records)
    • Delete each CNAME record:
      • Click the record → “Delete” → Confirm deletion
    • These typically look like: _abc123def456_xyz789ghi012.acm-validations.aws.

Phase 3: Delete Route 53 Hosted Zone

  1. Delete Hosted Zone
    • In Route 53 Console, go to “Hosted zones”
    • Select your hosted zone
    • Click “Delete hosted zone”
    • Confirm deletion when prompted
    • Important: This will delete the entire hosted zone and all remaining records

Cost Impact: This eliminates the $0.50/month Route 53 hosted zone charge.

Phase 4: Remove Cloudflare DNS Records

  1. Access Cloudflare Dashboard

  2. Delete NS Records

    • Find all NS records with name static
    • Delete each NS record:
      • Click the record → “Delete” → Confirm deletion
    • You should have 4 NS records to delete (one for each Route 53 nameserver)

Result: Your subdomain will no longer resolve to AWS services.

Phase 5: Delete S3 Bucket

  1. Navigate to S3 Console

  2. Empty Bucket (Required Before Deletion)

    • Click on your bucket name to open it
    • Select all objects (Ctrl+A or Cmd+A)
    • Click “Delete”
    • Type permanently delete to confirm
    • Click “Delete objects”
    • Wait for deletion to complete
  3. Delete Bucket

    • Go back to the bucket list
    • Select your bucket
    • Click “Delete”
    • Type the bucket name to confirm deletion
    • Click “Delete bucket”

Cost Impact: This eliminates S3 storage charges.

Phase 6: Delete ACM Certificate (Optional)

  1. Navigate to ACM Console

    • Go to AWS Certificate Manager
    • Important: Make sure you’re in the US East (N. Virginia) us-east-1 region
    • Find your certificate
  2. Delete Certificate

    • Select your certificate
    • Click “Delete”
    • Confirm deletion when prompted

Note: ACM certificates are free, so this step is optional but recommended for cleanup.

Phase 7: Delete Origin Access Control (OAC)

  1. Navigate to CloudFront Console

  2. Delete OAC

    • Select your OAC
    • Click “Delete”
    • Confirm deletion when prompted

Note: This step may not be necessary if the OAC was automatically deleted with the CloudFront distribution.

Verification Steps

Verify Resource Deletion

Run these commands to verify cleanup:

# Check if domain still resolves (should fail or point elsewhere)
nslookup static.yourdomain.com

# Check if CloudFront distribution is gone (should fail)
curl -I https://d1234abcd.cloudfront.net

Check AWS Billing

  1. Go to AWS Billing Console
  2. Check “Bills” to verify no charges for deleted resources
  3. Monitor for 24-48 hours to ensure no delayed charges

Expected Results After Cleanup

✅ What Should Be Deleted:

  • CloudFront distribution (no more CDN charges)
  • Route 53 hosted zone (no more $0.50/month charge)
  • S3 bucket and all objects (no more storage charges)
  • ACM certificate (was free anyway)
  • Origin Access Control (OAC)
  • Cloudflare NS records (DNS delegation removed)

✅ What Remains (Normal):

  • Your main domain in Cloudflare (unchanged)
  • Your local website files
  • Any other AWS resources not related to this project

Troubleshooting Common Cleanup Issues

CloudFront Distribution Won’t Delete

Problem: Distribution shows “Deploying” or won’t disable Solution:

  1. Wait up to 15 minutes for deployment to complete
  2. If stuck, contact AWS Support
  3. Ensure no Route 53 records are still pointing to it

S3 Bucket Won’t Delete

Problem: “Bucket not empty” error Solution:

  1. Go back to bucket contents
  2. Delete all objects and folders
  3. Check for versioned objects (if versioning was enabled)
  4. Delete all object versions

Route 53 Hosted Zone Won’t Delete

Problem: “Hosted zone contains records” error Solution:

  1. Delete all remaining records in the hosted zone
  2. Wait a few minutes for changes to propagate
  3. Try deletion again

Cost Monitoring

  1. Go to AWS Billing Console
  2. Click “Billing preferences”
  3. Enable “Receive Billing Alerts”
  4. Set up CloudWatch billing alarms for $1, $5, $10 thresholds

Monitor for 30 Days

  • Check AWS billing dashboard weekly
  • Look for any unexpected charges
  • Verify all resources are deleted in AWS console

Recovery (If Needed)

If you accidentally deleted something important:

  1. Website Files: Restore from your local directory
  2. S3 Bucket: Recreate using the same name (if available)
  3. CloudFront: Recreate distribution (will get new distribution ID)
  4. Route 53: Recreate hosted zone (will get new nameservers)
  5. DNS: Update Cloudflare with new Route 53 nameservers

Backup Recommendations

Before deletion, ensure you have:

  • Local copies of all website files
  • Screenshots of important configurations
  • List of all resource names and IDs
  • CloudFront distribution ID
  • Route 53 nameservers

Summary

Following this cleanup guide will eliminate all charges related to your S3 static website setup:

  • Monthly Savings: ~$0.50/month (Route 53 hosted zone)
  • Storage Savings: S3 storage costs (minimal but ongoing)
  • CDN Savings: CloudFront data transfer overages (if any)

The cleanup process should take approximately 30-60 minutes, with most time spent waiting for CloudFront distribution deletion and DNS propagation.

Remember: Keep your local website files safe - you can always redeploy using the original deployment guide if needed in the future.

Quick Reference Commands

Essential AWS CLI Commands

# Upload website files to S3
aws s3 sync ./your-website-directory/ s3://static.yourdomain.com/ --delete

# Create CloudFront invalidation
aws cloudfront create-invalidation --distribution-id YOUR_DISTRIBUTION_ID --paths "/*"

# Check CloudFront distribution status
aws cloudfront get-distribution --id YOUR_DISTRIBUTION_ID

# List S3 bucket contents
aws s3 ls s3://static.yourdomain.com/

# Check Route 53 hosted zones
aws route53 list-hosted-zones

# Verify SSL certificate
openssl s_client -connect static.yourdomain.com:443 -servername static.yourdomain.com

DNS Verification Commands

# Check DNS resolution
nslookup static.yourdomain.com
dig static.yourdomain.com

# Check CloudFront headers
curl -I https://static.yourdomain.com | grep -i cloudfront

# Test SSL certificate
curl -I https://static.yourdomain.com

Conclusion

This setup provides a production-ready, secure, and scalable static website hosting solution. The architecture ensures:

  • Performance: Global CDN with edge caching for fast content delivery
  • Security: Private S3 bucket with HTTPS encryption and Origin Access Control
  • Reliability: AWS managed services with high availability and automatic scaling
  • Cost-Effectiveness: Pay-as-you-go pricing with generous free tiers
  • Scalability: Automatic scaling with traffic growth

Key Benefits

  • Global Performance: CloudFront edge locations worldwide
  • Security: Multiple layers of protection with SSL/TLS encryption
  • Cost-Effective: Starting at ~$0.50/month with free tiers
  • Easy Management: Simple updates and content management
  • Professional Setup: Custom domain with SSL certificate

Next Steps

  1. Monitor your website performance and costs
  2. Set up monitoring and alerting
  3. Consider implementing additional security measures
  4. Optimize content for better performance
  5. Plan for backup and disaster recovery

For questions or issues, refer to the troubleshooting section or AWS documentation. This setup provides a solid foundation for hosting static websites with enterprise-grade reliability and performance.

Table of Contents