AWS S3 Static Website Production Deployment: Complete Guide with CloudFront, SSL, and Cleanup

A comprehensive production-ready guide for deploying static websites on AWS S3 with CloudFront CDN, SSL certificates, custom domains, and complete resource cleanup procedures

AWS S3 Static Website Production Deployment: Complete Guide with CloudFront, SSL, and Cleanup

Table of Contents

AWS S3 Static Website Production Deployment: Complete Guide with CloudFront, SSL, and Cleanup

Introduction

Deploying a production-ready static website on AWS requires more than just uploading files to S3. This comprehensive guide covers the complete lifecycle of hosting a static website on AWS, from initial deployment with CloudFront CDN and SSL certificates to proper resource cleanup to avoid ongoing charges.

What You’ll Learn

  • Production-grade S3 static website deployment with CloudFront CDN
  • SSL certificate setup using AWS Certificate Manager (ACM)
  • Custom domain configuration with Route 53 and Cloudflare
  • Security best practices for production environments
  • Performance optimization techniques
  • Complete resource cleanup procedures
  • Cost management and monitoring strategies

Architecture Overview

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

This architecture ensures:

  • Global CDN performance with CloudFront edge locations
  • HTTPS security with free SSL certificates
  • Private S3 bucket with Origin Access Control (OAC)
  • Custom domain support with proper DNS delegation

Prerequisites

Before starting, ensure you have:

  • AWS Account with administrative access
  • Domain registered (e.g., with Namecheap, GoDaddy, etc.)
  • Cloudflare account with DNS management access
  • Website files ready for deployment
  • Basic understanding of DNS and AWS services

Phase 1: S3 Bucket Creation and Configuration

1.1 Create S3 Bucket

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

Configure Bucket Settings

  1. Bucket name: static.yourdomain.com
    • Must be globally unique
    • Matches your subdomain for consistency
  2. AWS Region: Choose closest to your audience (e.g., us-east-1)
  3. Object Ownership: ACLs disabled (recommended)
  4. Block Public Access settings:
    • ✅ Block all public access (KEEP CHECKED)
    • ✅ Block public access to buckets and objects granted through new access control lists (ACLs)
    • ✅ Block public access to buckets and objects granted through any access control lists (ACLs)
    • ✅ Block public access to buckets and objects granted through new public bucket or access point policies
    • ✅ Block public access to buckets and objects granted through any public bucket or access point policies
  5. Bucket Versioning: Disable (unless needed)
  6. Default encryption: Amazon S3 managed keys (SSE-S3)
  7. Click “Create bucket”

1.2 Upload Website Files

Method 1: AWS Console

  1. Open your newly created bucket
  2. Click “Upload”
  3. Click “Add files” and select:
    • index.html
    • error.html
    • style.css
    • script.js
  4. Click “Upload” at the bottom
  5. Wait for upload completion
# Navigate to your project directory
cd your-website-directory

# Upload files to S3 with proper metadata
aws s3 sync ./ s3://static.yourdomain.com/ \
  --exclude ".*" \
  --cache-control "max-age=31536000" \
  --metadata-directive REPLACE

# Set proper content types for different file types
aws s3 cp ./index.html s3://static.yourdomain.com/ \
  --content-type "text/html" \
  --cache-control "max-age=3600"

aws s3 cp ./style.css s3://static.yourdomain.com/ \
  --content-type "text/css" \
  --cache-control "max-age=31536000"

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
    • Click “Add another name to this certificate”
    • Add: *.static.yourdomain.com (optional, for future subdomains)
  2. Validation method: Select “DNS validation”
  3. Key algorithm: RSA 2048 (default)
  4. Tags: Optional
  5. Click “Request”

2.3 Get Validation Records

  1. After requesting, click on the certificate ID to view details
  2. In the “Domains” section, you’ll see CNAME records needed for validation
  3. IMPORTANT: Copy the CNAME name and value
    • Example CNAME name: _abc123def456.static.yourdomain.com
    • Example CNAME value: _xyz789ghi012.acm-validations.aws.
  4. Do NOT create records yet - we need Route 53 hosted zone first

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. Description: Optional (e.g., “Static website subdomain”)
  6. Click “Create hosted zone”

3.2 Note Nameservers

  1. After creation, open the hosted zone
  2. You’ll see an NS (nameserver) record with 4 nameservers:
    ns-1224.awsdns-25.org
    ns-11.awsdns-01.com
    ns-981.awsdns-58.net
    ns-1661.awsdns-15.co.uk
    
  3. 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 .static.yourdomain.com suffix if present
    • Example: _abc123def456
  3. Record type: CNAME
  4. Value: Paste the CNAME value from Phase 2.3
    • Example: _xyz789ghi012.acm-validations.aws.
  5. TTL: 300 seconds
  6. Click “Create records”
  7. Repeat for any additional validation records if you added multiple domains

3.4 Wait for Certificate Validation

  1. Go back to ACM console
  2. Certificate status should change from “Pending validation” to “Issued”
  3. This typically takes 5-30 minutes
  4. 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 (this creates static.yourdomain.com)
  4. Nameserver: Paste the FIRST Route 53 nameserver
    • Example: ns-123.awsdns-45.com
  5. TTL: Auto
  6. Proxy status: DNS only (gray cloud) - DO NOT enable proxy
  7. Click “Save”
  8. Repeat steps 1-7 for the remaining 3 Route 53 nameservers

Result: You should have 4 NS records in Cloudflare:

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

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: Click dropdown and select your S3 bucket
  2. Origin path: Leave blank
  3. Name: Auto-filled, leave as is
  4. S3 bucket access:
    • Select “Yes use OAC (bucket can restrict access to only CloudFront)”
    • Click “Create control setting”
    • Name: Leave default or use static-domain-oac
    • Description: Optional
    • Click “Create”
  5. A policy will appear - Click “Copy policy” button and save to notepad
    • You’ll need this in Phase 6

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. Origin request policy: None (default)
  5. Response headers policy: None (default)
  6. Leave other cache settings as default

5.4 Settings

  1. Alternate domain name (CNAME):
    • Click “Add item”
    • Enter: static.yourdomain.com
  2. Custom SSL certificate: Select the ACM certificate you created
    • Should show static.yourdomain.com
  3. Default root object: Enter index.html
  4. Standard logging: Off (or enable if you want access logs)
  5. Price class: Use all edge locations (best performance)
  6. WAF: None (unless you need web application firewall)
  7. Leave other settings as default
  8. Click “Create distribution”

5.5 Wait for Deployment

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

Phase 6: Update S3 Bucket Policy

6.1 Apply CloudFront Policy

  1. Go back to S3 Console
  2. Open bucket
  3. Click “Permissions” tab
  4. Scroll to “Bucket policy” section → Click “Edit”
  5. Paste the policy you copied in Phase 5.2 step 5
  6. 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 from dropdown
  8. Routing policy: Simple routing
  9. Click “Create records”

Phase 8: Testing and Verification

8.1 Test CloudFront Distribution Directly

  1. In CloudFront console, copy your 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 the error page: 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

Production-Grade Enhancements

Performance Optimization

CloudFront Cache Behaviors

Configure different cache behaviors for different file types:

  1. HTML files: Short cache (1 hour)
  2. CSS/JS files: Long cache (1 year)
  3. Images: Medium cache (30 days)

Compression Settings

Enable compression in CloudFront for better performance:

  1. Go to CloudFront distribution
  2. Edit Cache behaviors
  3. Enable Compress objects automatically

HTTP/2 and HTTP/3

CloudFront automatically supports HTTP/2 and HTTP/3 for improved performance.

Security Enhancements

AWS WAF Integration

For production websites, consider enabling AWS WAF:

  1. Go to CloudFront distribution
  2. Edit Security
  3. Enable AWS WAF and select a WAF ACL

Security Headers

Add security headers using CloudFront response headers policy:

{
  "SecurityHeadersConfig": {
    "StrictTransportSecurity": {
      "Override": true,
      "AccessControlMaxAgeSec": 31536000,
      "IncludeSubdomains": true
    },
    "ContentTypeOptions": {
      "Override": true
    },
    "FrameOptions": {
      "Override": true,
      "FrameOption": "DENY"
    }
  }
}

Monitoring and Analytics

CloudWatch Metrics

Monitor key metrics:

  • Requests: Total number of requests
  • Data transfer: Amount of data transferred
  • Error rates: 4xx and 5xx error percentages
  • Cache hit ratio: Percentage of requests served from cache

Cost Monitoring

Set up billing alerts:

  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

Updating Website Content

Method 1: AWS Console

  1. Go to S3 → bucket
  2. Upload new/modified files (will overwrite existing)
  3. After upload, 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”
# 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 "/*"

Method 3: Automated Deployment Script

Create a deployment script for consistent updates:

#!/bin/bash
# deploy.sh - Automated deployment script

BUCKET_NAME="static.yourdomain.com"
DISTRIBUTION_ID="YOUR_DISTRIBUTION_ID"
WEBSITE_DIR="./website"

echo "Deploying to S3..."
aws s3 sync $WEBSITE_DIR s3://$BUCKET_NAME/ --delete

echo "Invalidating CloudFront cache..."
aws cloudfront create-invalidation --distribution-id $DISTRIBUTION_ID --paths "/*"

echo "Deployment complete!"

Cost Management

Monthly Cost Breakdown

  • 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 Strategies

  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
  5. Set up billing alerts for unexpected charges

Complete Resource Cleanup Guide

⚠️ Important Notes Before Cleanup

  1. Backup Important Data: Ensure you have local copies of your website files
  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

Cleanup Order (Critical - Follow This Sequence)

Phase 1: Delete CloudFront Distribution

  1. Go to AWS CloudFront Console
  2. Find your distribution
  3. Status must be “Enabled” - if it shows “Deploying”, wait for it to complete
  4. Click on your distribution ID to open it
  5. Click the “Disable” button
  6. Confirm the action when prompted
  7. Wait for status to change to “Disabled” (this can take 5-15 minutes)
  8. Once status shows “Disabled”, click the “Delete” button
  9. Type the distribution ID to confirm deletion
  10. Click “Delete” to confirm
  11. Wait for deletion to complete

Phase 2: Delete Route 53 DNS Records

  1. Go to Route 53 Console
  2. Click “Hosted zones”
  3. Select your hosted zone
  4. Find the A record (should point to CloudFront distribution)
  5. Click the record → “Delete” → Confirm deletion
  6. Find any CNAME records that start with _ (ACM validation records)
  7. Delete each CNAME record

Phase 3: Delete Route 53 Hosted Zone

  1. In Route 53 Console, go to “Hosted zones”
  2. Select your hosted zone
  3. Click “Delete hosted zone”
  4. Confirm deletion when prompted
  5. 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. Go to Cloudflare Dashboard
  2. Select your domain
  3. Go to “DNS”“Records”
  4. Find all NS records with name static
  5. Delete each NS record
  6. You should have 4 NS records to delete (one for each Route 53 nameserver)

Phase 5: Delete S3 Bucket

  1. Go to AWS S3 Console
  2. Find your bucket
  3. Click on your bucket name to open it
  4. Select all objects (Ctrl+A or Cmd+A)
  5. Click “Delete”
  6. Type permanently delete to confirm
  7. Click “Delete objects”
  8. Wait for deletion to complete
  9. Go back to the bucket list
  10. Select your bucket
  11. Click “Delete”
  12. Type the bucket name to confirm deletion
  13. Click “Delete bucket”

Phase 6: Delete ACM Certificate (Optional)

  1. Go to AWS Certificate Manager
  2. Important: Make sure you’re in the US East (N. Virginia) us-east-1 region
  3. Find your certificate
  4. Select your certificate
  5. Click “Delete”
  6. 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. Go to AWS CloudFront Console
  2. Click “Origin access” in the left sidebar
  3. Find your OAC
  4. Select your OAC
  5. Click “Delete”
  6. Confirm deletion when prompted

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

Troubleshooting Common Issues

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

DNS Not Resolving

Problem: Domain doesn’t resolve to CloudFront

Solution:

  1. Verify NS records in Cloudflare match Route 53 nameservers exactly
  2. Check for typos in domain names
  3. Ensure Cloudflare proxy is disabled (gray cloud)

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

Advanced Production Features

Multi-Environment Setup

Development Environment

# Development bucket
aws s3 mb s3://dev-static.yourdomain.com

# Development CloudFront distribution
aws cloudfront create-distribution --distribution-config file://dev-distribution.json

Staging Environment

# Staging bucket
aws s3 mb s3://staging-static.yourdomain.com

# Staging CloudFront distribution
aws cloudfront create-distribution --distribution-config file://staging-distribution.json

CI/CD Integration

GitHub Actions Example

name: Deploy Static Website

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: us-east-1

      - name: Deploy to S3
        run: |
          aws s3 sync ./dist s3://static.yourdomain.com/ --delete          

      - name: Invalidate CloudFront
        run: |
          aws cloudfront create-invalidation --distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} --paths "/*"          

Backup and Disaster Recovery

Cross-Region Replication

# Enable versioning on source bucket
aws s3api put-bucket-versioning --bucket static.yourdomain.com --versioning-configuration Status=Enabled

# Create replication rule
aws s3api put-bucket-replication --bucket static.yourdomain.com --replication-configuration file://replication.json

Automated Backups

#!/bin/bash
# backup.sh - Automated backup script

BACKUP_BUCKET="backup-static.yourdomain.com"
SOURCE_BUCKET="static.yourdomain.com"
DATE=$(date +%Y%m%d)

# Create backup
aws s3 sync s3://$SOURCE_BUCKET s3://$BACKUP_BUCKET/backup-$DATE/

# Clean up old backups (keep last 30 days)
aws s3 ls s3://$BACKUP_BUCKET/ | grep "backup-" | head -n -30 | awk '{print $2}' | xargs -I {} aws s3 rm s3://$BACKUP_BUCKET/{}

Best Practices Summary

Security Best Practices

  1. Always use HTTPS - Redirect HTTP to HTTPS
  2. Private S3 buckets - No direct public access
  3. Origin Access Control (OAC) - CloudFront-only access to S3
  4. Security headers - Implement CSP, HSTS, etc.
  5. Regular security audits - Monitor access logs

Performance Best Practices

  1. Optimize images - Use WebP format when possible
  2. Minify CSS/JS - Reduce file sizes
  3. Enable compression - Gzip/Brotli compression
  4. Cache optimization - Appropriate TTL settings
  5. CDN utilization - Leverage CloudFront edge locations

Cost Optimization Best Practices

  1. Monitor usage - Set up billing alerts
  2. Optimize cache settings - Reduce origin requests
  3. Use appropriate storage classes - Standard vs IA
  4. Regular cleanup - Remove unused resources
  5. Cost analysis - Regular cost reviews

Conclusion

This comprehensive guide covers the complete lifecycle of deploying and managing a production-ready static website on AWS. From initial deployment with CloudFront CDN and SSL certificates to proper resource cleanup, you now have all the knowledge needed to:

  • Deploy a secure, scalable static website
  • Optimize performance and costs
  • Monitor and maintain the infrastructure
  • Clean up resources when no longer needed

Key Takeaways

  1. Security First: Always use private S3 buckets with OAC
  2. Performance Matters: Leverage CloudFront CDN for global performance
  3. Cost Management: Monitor usage and set up billing alerts
  4. Proper Cleanup: Follow the exact sequence to avoid dependency issues
  5. Automation: Use CI/CD pipelines for consistent deployments

Next Steps

  1. Practice with a test domain first
  2. Implement monitoring and alerting
  3. Set up automated deployments
  4. Document your specific configurations
  5. Regular security and cost reviews

Remember: This setup provides a production-ready, secure, and scalable static website hosting solution that can handle traffic spikes while maintaining security and performance standards.

For questions or issues, refer to the troubleshooting section or AWS documentation. Happy deploying!

Table of Contents