Hosting Static Sites with S3 and CloudFront
Static site hosting on AWS is incredibly powerful. By combining S3 for storage and CloudFront for global distribution, you can build sites that are fast, secure, and cost pennies per month.
Architecture Overview
The setup is straightforward:
User → CloudFront → S3 Bucket
↓
Edge Locations (Global)
Benefits:
- Global CDN: Content cached at edge locations worldwide
- HTTPS by default: SSL/TLS certificates via ACM
- Low cost: Pay only for what you use
- High availability: AWS's infrastructure reliability
Step-by-Step Setup
1. Create an S3 Bucket
aws s3 mb s3://my-website-bucket --region us-east-1
Configure for static hosting:
aws s3 website s3://my-website-bucket/ \
--index-document index.html \
--error-document error.html
2. Set Bucket Policy
Allow CloudFront to access your content:
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "AllowCloudFrontAccess",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-website-bucket/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::ACCOUNT:distribution/DIST-ID"
}
}
}]
}
3. Create CloudFront Distribution
Configure with:
- Origin: Your S3 bucket
- Default root object:
index.html - Viewer protocol: Redirect HTTP to HTTPS
- Compress objects automatically
- Custom SSL certificate (optional)
4. Configure DNS
Point your domain to CloudFront:
CNAME: www.example.com → d111111abcdef8.cloudfront.net
A: example.com → CloudFront alias
Optimization Strategies
Cache Control Headers
Set appropriate headers for different file types:
# Cache HTML for 5 minutes
aws s3 cp index.html s3://bucket/ \
--cache-control "max-age=300"
# Cache images for 1 year
aws s3 cp images/ s3://bucket/images/ \
--recursive \
--cache-control "max-age=31536000"
# Cache CSS/JS with fingerprints forever
aws s3 cp assets/ s3://bucket/assets/ \
--recursive \
--cache-control "max-age=31536000,immutable"
File Compression
Enable compression in CloudFront:
- Gzip for text-based files
- Automatic compression for HTML, CSS, JS, JSON
This can reduce transfer sizes by 70%+.
Smart Invalidations
Invalidate only what changed:
# Invalidate specific files
aws cloudfront create-invalidation \
--distribution-id E1234EXAMPLE \
--paths "/index.html" "/about.html"
# Invalidate entire directory
aws cloudfront create-invalidation \
--distribution-id E1234EXAMPLE \
--paths "/blog/*"
Security Best Practices
Block Direct S3 Access
Use Origin Access Control (OAC):
- Create an OAC in CloudFront
- Associate with your distribution
- Update S3 bucket policy to allow only OAC
Enable WAF
Protect against common attacks:
CloudFront → AWS WAF → Rules
- Rate limiting
- SQL injection protection
- XSS prevention
- Geo-blocking
Security Headers
Add headers via CloudFront Functions:
function handler(event) {
var response = event.response;
var headers = response.headers;
headers['strict-transport-security'] = {
value: 'max-age=31536000; includeSubdomains'
};
headers['x-content-type-options'] = {
value: 'nosniff'
};
headers['x-frame-options'] = {
value: 'DENY'
};
headers['x-xss-protection'] = {
value: '1; mode=block'
};
return response;
}
CI/CD Integration
Automate deployments with GitHub Actions:
name: Deploy to S3
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build
run: npm run build
- name: Deploy to S3
run: |
aws s3 sync ./out s3://my-bucket \
--delete \
--cache-control max-age=31536000
- name: Invalidate CloudFront
run: |
aws cloudfront create-invalidation \
--distribution-id ${{ secrets.DIST_ID }} \
--paths "/*"
Cost Optimization
Reduce Costs
- Use S3 Intelligent-Tiering: Automatic cost savings
- Enable CloudFront compression: Reduce bandwidth
- Set appropriate TTLs: Fewer origin requests
- Use CloudFront Functions: Cheaper than Lambda@Edge
- Monitor usage: Track costs in Cost Explorer
Typical Costs
For a blog with 10,000 monthly visitors:
- S3 storage (1GB): $0.02
- S3 requests: $0.05
- CloudFront data transfer: $0.85
- Total: ~$1/month
Common Pitfalls
SPA Routing Issues
For single-page apps, configure custom error responses:
- Error code: 403
- Response page:
/index.html - Response code: 200
Cache Invalidation Costs
First 1,000 paths/month are free. Beyond that:
- $0.005 per path
Use wildcards strategically:
- ❌ Bad: Invalidate every file individually
- ✅ Good: Invalidate
/blog/*for all blog updates
Certificate Regions
ACM certificates for CloudFront MUST be in us-east-1.
Monitoring and Debugging
CloudWatch Metrics
Track:
- Requests per second
- Error rate (4xx, 5xx)
- Cache hit ratio
- Data transfer
CloudFront Logs
Enable access logs for detailed analytics:
Date Time Edge-Location Bytes IP Method Host URI Status
Real User Monitoring
Integrate tools like:
- CloudWatch RUM
- Google Analytics
- Custom performance tracking
Conclusion
S3 and CloudFront provide enterprise-grade static hosting at consumer prices. With proper configuration, you get global performance, strong security, and minimal operational overhead.
The key is understanding caching strategies and optimizing for your specific use case. Get these right, and you'll have a site that's fast, reliable, and costs next to nothing to run.