S3 and CloudFront: Efficient Content Distribution
Discover how to use Amazon S3 for static content storage and CloudFront as a global CDN. Learn about buckets, access policies, static hosting, CloudFront distributions, Origin Access Control, cache invalidation, SSL/TLS, and cost optimization with Route 53.
Written by Francisco Zapata
Amazon S3 (Simple Storage Service) and Amazon CloudFront form a powerful combination for storing and delivering content globally, quickly, and securely. S3 stores your files with extremely high durability (99.999999999%, often called "eleven nines") while CloudFront distributes them from over 450 points of presence worldwide. In this guide, you will learn how to configure both services to host static websites, serve assets, and optimize content delivery.
What Is Amazon S3?
S3 is an object storage service that provides scalability, data availability, security, and performance. It stores data as objects within buckets. Each object can be up to 5 TB in size and is identified by a unique key within the bucket.
Creating an S3 Bucket
# Create a bucket in us-east-1
aws s3 mb s3://my-example-website --region us-east-1
# Verify it was created
aws s3 ls
Bucket names must be globally unique across all of AWS, use lowercase letters, and cannot contain spaces.
Setting Up Static Hosting on S3
S3 can directly serve a static website:
# Enable static website hosting
aws s3 website s3://my-example-website \
--index-document index.html \
--error-document 404.html
# Upload files to the bucket
aws s3 sync ./dist s3://my-example-website \
--delete \
--cache-control "max-age=86400"
To make the site publicly accessible, you need to disable the block public access setting and add a bucket policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-example-website/*"
}
]
}
# Apply the policy
aws s3api put-bucket-policy \
--bucket my-example-website \
--policy file://bucket-policy.json
Warning: Disabling the block public access setting makes your files accessible to anyone on the internet. The AWS-recommended approach is to keep block public access enabled and use CloudFront with Origin Access Control.
What Is Amazon CloudFront?
CloudFront is AWS's content delivery network (CDN). It caches your content at edge locations around the world so users receive files from the nearest point, significantly reducing latency.
Key Benefits of CloudFront:
- Native HTTPS: S3 static websites only support HTTP; CloudFront adds HTTPS
- Better performance: Caching at global edge locations
- Security: Built-in DDoS protection with AWS Shield
- Access control: Origin Access Control (OAC) to protect your bucket
Creating a CloudFront Distribution with OAC
Origin Access Control (OAC) is the AWS-recommended method to ensure only CloudFront can access your S3 bucket, keeping the block public access setting enabled:
# Create an Origin Access Control
aws cloudfront create-origin-access-control \
--origin-access-control-config '{
"Name": "my-site-oac",
"Description": "OAC for my website",
"SigningProtocol": "sigv4",
"SigningBehavior": "always",
"OriginAccessControlOriginType": "s3"
}'
Then create the distribution with a JSON configuration file:
{
"CallerReference": "my-website-2025",
"Origins": {
"Quantity": 1,
"Items": [
{
"Id": "S3-my-website",
"DomainName": "my-example-website.s3.us-east-1.amazonaws.com",
"S3OriginConfig": {
"OriginAccessIdentity": ""
},
"OriginAccessControlId": "EXXXXXXXXXXXXXXX"
}
]
},
"DefaultCacheBehavior": {
"TargetOriginId": "S3-my-website",
"ViewerProtocolPolicy": "redirect-to-https",
"AllowedMethods": ["GET", "HEAD"],
"CachedMethods": ["GET", "HEAD"],
"ForwardedValues": {
"QueryString": false,
"Cookies": { "Forward": "none" }
},
"MinTTL": 0,
"DefaultTTL": 86400,
"MaxTTL": 31536000,
"Compress": true
},
"DefaultRootObject": "index.html",
"Enabled": true,
"Comment": "Distribution for my website"
}
# Create the distribution
aws cloudfront create-distribution \
--distribution-config file://distribution-config.json
Update the bucket policy to allow access only from CloudFront:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCloudFrontServicePrincipal",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-example-website/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::123456789012:distribution/EXXXXXXXXXXXXXXX"
}
}
}
]
}
Configuring SSL/TLS with a Custom Certificate
To use your own domain with HTTPS, request a certificate in AWS Certificate Manager (ACM). It must be in us-east-1 to work with CloudFront:
# Request an SSL certificate in ACM (us-east-1 region)
aws acm request-certificate \
--domain-name your-domain.com \
--subject-alternative-names "*.your-domain.com" \
--validation-method DNS \
--region us-east-1
Validate the certificate by creating the CNAME records that ACM provides in your DNS.
Cache Invalidation
When you update your site, you need to invalidate CloudFront's cache so changes are reflected immediately:
# Invalidate specific files
aws cloudfront create-invalidation \
--distribution-id EXXXXXXXXXXXXXXX \
--paths "/index.html" "/css/*" "/js/*"
# Invalidate the entire site (useful during deploys)
aws cloudfront create-invalidation \
--distribution-id EXXXXXXXXXXXXXXX \
--paths "/*"
# Check invalidation status
aws cloudfront get-invalidation \
--distribution-id EXXXXXXXXXXXXXXX \
--id IXXXXXXXXXXXXXXX
Cost note: The first 1,000 invalidation paths per month are free. After that, each path costs $0.005 USD.
Setting Up Route 53 as DNS
Route 53 lets you create an alias record that points directly to your CloudFront distribution at no cost for alias-type DNS queries:
# Create an alias record pointing to CloudFront
aws route53 change-resource-record-sets \
--hosted-zone-id Z1234567890 \
--change-batch '{
"Changes": [{
"Action": "CREATE",
"ResourceRecordSet": {
"Name": "your-domain.com",
"Type": "A",
"AliasTarget": {
"HostedZoneId": "Z2FDTNDATAQYW2",
"DNSName": "d1234567890.cloudfront.net",
"EvaluateTargetHealth": false
}
}
}]
}'
The HostedZoneId value Z2FDTNDATAQYW2 is the fixed hosted zone ID for CloudFront.
Complete Deployment Script
Here is a script that automates the entire deployment process:
#!/bin/bash
BUCKET="my-example-website"
DISTRIBUTION_ID="EXXXXXXXXXXXXXXX"
# Build the project
npm run build
# Sync files to S3 with long cache
aws s3 sync ./dist s3://$BUCKET \
--delete \
--cache-control "max-age=31536000,public" \
--exclude "index.html" \
--exclude "*.json"
# Upload HTML and JSON with short cache
aws s3 sync ./dist s3://$BUCKET \
--cache-control "max-age=0,no-cache,no-store,must-revalidate" \
--include "index.html" \
--include "*.json"
# Invalidate CloudFront cache
aws cloudfront create-invalidation \
--distribution-id $DISTRIBUTION_ID \
--paths "/*"
echo "Deployment completed successfully"
Estimated Costs
| Service | Free Tier | After Free Tier |
|---|---|---|
| S3 Storage | 5 GB/month (12 months) | $0.023/GB/month |
| S3 Requests | 20,000 GET/month | $0.0004/1,000 GET |
| CloudFront | 1 TB transfer/month | $0.085/GB (first 10 TB) |
| Route 53 | Not included | $0.50/zone + $0.40/million queries |
| ACM | Free | Free when used with AWS services |
Conclusion
The combination of S3 and CloudFront is the go-to solution for serving static content on AWS. With Origin Access Control you keep your bucket secure, CloudFront provides HTTPS and global distribution, and Route 53 delivers reliable DNS. This architecture is highly scalable, cost-effective, and requires minimal operational overhead.
Comments (0)
Leave a comment
Be the first to comment