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
- Go to AWS S3 Console
- Click “Create bucket”
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
- Open your newly created bucket
- Click “Upload” → “Add files”
- Select your website files (index.html, CSS, JS, images)
- 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
- Go to AWS Certificate Manager
- CRITICAL: Switch region to US East (N. Virginia) us-east-1
- CloudFront requires certificates in this region only
- Click “Request certificate”
- Select “Request a public certificate” → Click “Next”
2.2 Configure Domain Names
- Domain names:
- Add:
static.yourdomain.com - Optionally add:
*.static.yourdomain.com for future subdomains
- Validation method: Select “DNS validation”
- Key algorithm: RSA 2048 (default)
- Click “Request”
2.3 Get Validation Records
- Click on the certificate ID to view details
- In the “Domains” section, copy the CNAME records needed for validation
- 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
- Go to Route 53 Console
- Click “Hosted zones” → “Create hosted zone”
- Domain name:
static.yourdomain.com - Type: Public hosted zone
- 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
- In your Route 53 hosted zone, click “Create record”
- Record name: Paste the CNAME name from Phase 2.3 (remove domain suffix)
- Record type: CNAME
- Value: Paste the CNAME value from Phase 2.3
- TTL: 300 seconds
- 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
- Go to Cloudflare Dashboard
- Select your domain
- Go to “DNS” → “Records”
4.2 Create NS Records
- Click “Add record”
- Type: NS
- Name:
static (creates static.yourdomain.com) - Nameserver: Paste the FIRST Route 53 nameserver
- TTL: Auto
- Proxy status: DNS only (gray cloud) - DO NOT enable proxy
- Click “Save”
- 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
- Go to CloudFront Console
- Click “Create distribution”
5.2 Origin Settings
- Origin domain: Select your S3 bucket
static.yourdomain.com - Origin path: Leave blank
- S3 bucket access:
- Select “Yes use OAC (bucket can restrict access to only CloudFront)”
- Click “Create control setting”
- Name:
static-domain-oac - Click “Create”
- Copy the generated policy - you’ll need this for S3 bucket policy
5.3 Default Cache Behavior Settings
- Viewer protocol policy: Select “Redirect HTTP to HTTPS”
- Allowed HTTP methods: GET, HEAD (default)
- Cache policy: CachingOptimized (default)
- Leave other settings as default
5.4 Settings
- Alternate domain name (CNAME):
- Add:
static.yourdomain.com
- Custom SSL certificate: Select your ACM certificate
- Default root object: Enter
index.html - Price class: Use all edge locations (best performance)
- 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
- Go to S3 Console → Your bucket
- Click “Permissions” tab
- Scroll to “Bucket policy” section → Click “Edit”
- Paste the policy you copied in Phase 5.2
- 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"
}
}
}
]
}
- Click “Save changes”
Phase 7: Route 53 DNS Records for CloudFront
7.1 Create A Record
- Go to Route 53 → Hosted zones
- Select your hosted zone
- Click “Create record”
- Record name: Leave BLANK (creates record for static.yourdomain.com)
- Record type: A
- Alias: Toggle ON
- Route traffic to:
- Select “Alias to CloudFront distribution”
- Select your CloudFront distribution
- Click “Create records”
Phase 8: Testing and Verification
8.1 Test CloudFront Distribution Directly
- Copy your CloudFront distribution domain name
- Open in browser:
https://d1234abcd.cloudfront.net - Verify your website loads correctly
8.2 Test Custom Domain
Wait 5-10 minutes for DNS propagation, then:
- Open browser:
https://static.yourdomain.com - Verify your website loads correctly
- Check that HTTPS works (look for padlock icon)
- 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
- Go to S3 → Your bucket
- Upload new/modified files (will overwrite existing)
- 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
- Use CloudFront caching to reduce S3 requests
- Enable compression in CloudFront
- Use appropriate cache TTL settings
- Monitor usage in AWS Cost Explorer
Security Best Practices
Implemented Security Features
- Private S3 Bucket: No direct public access
- Origin Access Control (OAC): CloudFront-only access to S3
- HTTPS Only: Automatic HTTP to HTTPS redirect
- SSL/TLS Certificate: Free ACM certificate with auto-renewal
- DNS Security: Cloudflare DNS protection
Additional Security Recommendations
- Enable AWS WAF if needed for additional protection
- Set up CloudTrail for audit logging
- Use IAM roles with minimal permissions
- Regular security audits and monitoring
- Monitor access logs and set up alerts
Troubleshooting
Common Issues and Solutions
Certificate Not Validating
Problem: ACM certificate stuck in “Pending validation”
Solution:
- Check CNAME records in Route 53 match ACM requirements exactly
- Ensure no typos in domain names
- Wait up to 30 minutes for propagation
Website Not Loading via Custom Domain
Problem: 404 or DNS resolution errors
Solution:
- Verify Route 53 A record points to correct CloudFront distribution
- Check Cloudflare NS records are correct
- Wait for DNS propagation (up to 48 hours globally)
403 Forbidden Error
Problem: CloudFront returns 403 when accessing S3 content
Solution:
- Verify S3 bucket policy is correctly applied
- Check OAC is properly configured
- Ensure CloudFront distribution ARN matches bucket policy
Old Content Showing After Update
Problem: Changes not visible after uploading new files
Solution:
- Create CloudFront invalidation for updated files
- Check cache TTL settings
- 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
CloudFront Settings
- Cache Behaviors: Optimize TTL settings for different content types
- Compression: Enable gzip compression for text files
- HTTP/2: Automatically enabled for better performance
- Edge Locations: Use all edge locations for best global performance
Website Optimization
- Minify CSS/JS: Reduce file sizes
- Optimize Images: Use WebP format when possible
- Lazy Loading: Implement for images and content
- CDN Caching: Leverage CloudFront edge locations
Monitoring and Analytics
AWS CloudWatch
- Monitor CloudFront metrics (requests, errors, cache hit ratio)
- Set up billing alerts for cost monitoring
- Track error rates and performance metrics
Third-Party Analytics
- Google Analytics for website traffic analysis
- Cloudflare Analytics (if using Cloudflare proxy)
- Custom analytics solutions
Backup and Recovery
S3 Versioning
- Enable versioning for important files
- Set up lifecycle policies for cost optimization
- Cross-region replication (if needed for disaster recovery)
Disaster Recovery
- Keep local copies of website files
- Document all configurations and settings
- 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
- Backup Important Data: Ensure you have local copies of your website files before deletion
- DNS Propagation: DNS changes may take up to 48 hours to propagate globally
- Irreversible Actions: Some deletions are permanent and cannot be undone
- 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
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
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)
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
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
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
- 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
Access Cloudflare Dashboard
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
Navigate to S3 Console
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
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)
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
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)
Navigate to CloudFront Console
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
- Go to AWS Billing Console
- Check “Bills” to verify no charges for deleted resources
- 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:
- Wait up to 15 minutes for deployment to complete
- If stuck, contact AWS Support
- Ensure no Route 53 records are still pointing to it
S3 Bucket Won’t Delete
Problem: “Bucket not empty” error
Solution:
- Go back to bucket contents
- Delete all objects and folders
- Check for versioned objects (if versioning was enabled)
- Delete all object versions
Route 53 Hosted Zone Won’t Delete
Problem: “Hosted zone contains records” error
Solution:
- Delete all remaining records in the hosted zone
- Wait a few minutes for changes to propagate
- Try deletion again
Cost Monitoring
Set Up Billing Alerts (Recommended)
- Go to AWS Billing Console
- Click “Billing preferences”
- Enable “Receive Billing Alerts”
- 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:
- Website Files: Restore from your local directory
- S3 Bucket: Recreate using the same name (if available)
- CloudFront: Recreate distribution (will get new distribution ID)
- Route 53: Recreate hosted zone (will get new nameservers)
- 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
- Monitor your website performance and costs
- Set up monitoring and alerting
- Consider implementing additional security measures
- Optimize content for better performance
- 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.