CloudFront CDN: Setup and Optimization Guide
AWS CloudFront is a content delivery network (CDN) that delivers data, video, and applications to users globally with low latency and high transfer speeds — using 600+ edge locations worldwide.
What You’ll Learn
- Setting up a CloudFront distribution with custom origins
- Configuring caching policies, behaviors, and TTLs
- Securing content with SSL/TLS, WAF, and signed URLs
- Customizing content with Lambda@Edge
- Monitoring, logging, and cost optimization
Why CloudFront Matters
Serving content from a single region means users in other continents wait 200-500ms for every request. CloudFront caches content at 600+ edge locations, reducing latency to under 20ms for most users. It also reduces origin load (fewer requests hit your servers) and provides DDoS protection. DodaTech uses CloudFront to distribute Durga Antivirus Pro signature updates — users in 190+ countries download updates from their nearest edge location, reducing download times by 80%.
flowchart LR
A[AWS Fundamentals] --> B[CloudFront CDN]
B --> C[Distributions]
B --> D[Origins]
B --> E[Caching]
B --> F[Security]
C --> G[Edge Locations]
D --> H[S3 / ALB / Custom]
E --> I[TTL Policies]
F --> J[WAF / Signed URLs]
style B fill:#f90,color:#fff
Setting Up a Distribution
Distribution with S3 Origin
# Create CloudFront distribution for S3 bucket
aws cloudfront create-distribution \
--origin-domain-name dodatech-site.s3.us-east-1.amazonaws.com \
--default-root-object index.html \
--enabled \
--comment "DodaTech website"
# Output:
# {
# "Distribution": {
# "Id": "E1XAMPLE12345",
# "DomainName": "d1234example.cloudfront.net",
# "Status": "InProgress"
# }
# }Terraform Configuration
# cloudfront.tf
resource "aws_cloudfront_distribution" "site" {
origin {
domain_name = aws_s3_bucket.site.bucket_regional_domain_name
origin_id = "S3-site"
s3_origin_config {
origin_access_identity = aws_cloudfront_origin_access_identity.oai.cloudfront_access_identity_path
}
}
enabled = true
is_ipv6_enabled = true
default_root_object = "index.html"
price_class = "PriceClass_100" # Only US/Europe (cheaper)
default_cache_behavior {
allowed_methods = ["GET", "HEAD", "OPTIONS"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "S3-site"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "redirect-to-https"
min_ttl = 0
default_ttl = 3600 # 1 hour
max_ttl = 86400 # 1 day
compress = true
}
restrictions {
geo_restriction {
restriction_type = "none"
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
tags = {
Name = "dodatech-site"
Environment = "production"
}
}Caching Policies
Cache Behavior Routing
Route different paths to different origins with different TTLs:
# API behavior — no cache, forward all headers
ordered_cache_behavior {
path_pattern = "/api/*"
target_origin_id = "ALB-origin"
allowed_methods = ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"]
cached_methods = ["GET", "HEAD"]
forwarded_values {
query_string = true
headers = ["Authorization", "Content-Type"]
cookies {
forward = "all"
}
}
viewer_protocol_policy = "https-only"
min_ttl = 0
default_ttl = 0
max_ttl = 0
}
# Static assets — long cache, compress
ordered_cache_behavior {
path_pattern = "/static/*"
target_origin_id = "S3-site"
viewer_protocol_policy = "redirect-to-https"
default_ttl = 2592000 # 30 days
compress = true
}Custom Error Responses
# Show custom error pages instead of generic CloudFront errors
custom_error_response {
error_code = 403
response_code = 200
response_page_path = "/error.html" # SPA fallback
}
custom_error_response {
error_code = 404
response_code = 200
response_page_path = "/error.html"
}Security: WAF and Signed URLs
WAF Integration
resource "aws_wafv2_web_acl" "cloudfront" {
name = "cloudfront-waf"
scope = "CLOUDFRONT"
default_action {
allow {}
}
rule {
name = "rate-limit"
priority = 1
action {
block {}
}
statement {
rate_based_statement {
limit = 5000
aggregate_key_type = "IP"
}
}
}
rule {
name = "block-bad-ips"
priority = 2
action {
block {}
}
statement {
ip_set_reference_statement {
arn = aws_wafv2_ip_set.blocked.arn
}
}
}
}
resource "aws_cloudfront_distribution" "site" {
web_acl_id = aws_wafv2_web_acl.cloudfront.arn
# ...
}Signed URLs for Private Content
# generate_signed_url.py
import boto3
from datetime import datetime, timedelta
def generate_signed_url(distribution_url, private_key_path, key_pair_id, expire_hours=24):
signer = boto3.client('cloudfront')
url = f"{distribution_url}/private/signatures-v2.db"
expiry = datetime.now() + timedelta(hours=expire_hours)
signed_url = signer.sign_url(
url=url,
keypair_id=key_pair_id,
private_key=private_key_path,
expire_time=expiry
)
return signed_url
url = generate_signed_url(
"https://d1234example.cloudfront.net",
"/path/to/private-key.pem",
"KEXAMPLE12345"
)
print(f"Signed URL (expires in 24h):\n{url}")
# Output:
# https://d1234example.cloudfront.net/private/signatures-v2.db?...
# Expires=1718899200&Signature=abc123...&Key-Pair-Id=KEXAMPLE12345Lambda@Edge
Customize content at edge locations:
# lambda_edge_viewer_request.py
# Redirect mobile users to mobile site
import json
def lambda_handler(event, context):
request = event['Records'][0]['cf']['request']
headers = request['headers']
# Check User-Agent for mobile
if 'user-agent' in headers:
ua = headers['user-agent'][0]['value'].lower()
if any(w in ua for w in ['mobile', 'android', 'iphone']):
response = {
'status': '302',
'statusDescription': 'Found',
'headers': {
'location': [{
'key': 'Location',
'value': f"https://m.example.com{request['uri']}"
}]
}
}
return response
return requestLogging and Monitoring
# Enable CloudFront logging
resource "aws_cloudfront_distribution" "site" {
logging_config {
include_cookies = false
bucket = aws_s3_bucket.logs.bucket_domain_name
prefix = "cloudfront/"
}
}# CloudFront metrics in CloudWatch / Prometheus
# Total requests per distribution
aws_cloudfront_requests_sum{distribution_id="E1XAMPLE12345"}
# Error rate (4xx + 5xx)
aws_cloudfront_error_rate_sum{distribution_id="E1XAMPLE12345"}
# Data transfer in GB
aws_cloudfront_bytes_downloaded_sum{distribution_id="E1XAMPLE12345"}Cost Optimization
| Strategy | Savings | Implementation |
|---|---|---|
| Price Class | 30-50% | Use PriceClass_100 (US+Europe) instead of PriceClass_All |
| Compression | 30-60% bandwidth | Enable compress = true for text content |
| Cache hit ratio | 50-80% origin offload | Increase TTLs, use cache-control headers |
| S3 origin access | 0 data transfer cost | Use OAI to keep S3 private |
Common Mistakes
No cache invalidation strategy: When content changes, old cached versions serve stale data. Use versioned filenames (
style.v2.css) instead of invalidating. Invalidation costs $0.005/path and takes 5-15 minutes.Not enabling compression: Text files (HTML, CSS, JS) compress 60-80%. Without compression, users download larger files and bandwidth costs increase. Enable
compress = true.Using all edge locations unnecessarily: If your users are only in the US and Europe, use
PriceClass_100. Including Asia, South America, and Australia increases cost 2x without benefit.Serving private content without signed URLs: Without signed URLs, any user who guesses a CloudFront URL can access private content. Always use signed URLs or cookies for authenticated content.
No custom error pages: CloudFront’s default error pages are ugly and don’t match your brand. Configure custom error responses that redirect to your own error pages.
Practice Questions
What is the difference between CloudFront and S3? Answer: S3 is object storage. CloudFront is a CDN that caches content at edge locations. CloudFront sits in front of S3 (or other origins) to serve content with low latency globally.
How does CloudFront handle cache invalidation? Answer: You can invalidate specific paths via console, CLI, or API. Invalidations cost $0.005 per path and take 5-15 minutes to propagate. Better approach: use versioned filenames.
What is Origin Access Identity (OAI)? Answer: OAI is a virtual identity that CloudFront uses to access your S3 bucket. With OAI, only CloudFront can access the bucket — users can’t bypass the CDN and access S3 directly.
What is Lambda@Edge used for? Answer: Lambda@Edge runs code at CloudFront edge locations in response to viewer or origin requests/responses. Use cases: A/B testing, URL rewrites, authentication, header modification.
Challenge
Set up a production CloudFront distribution: create an S3 bucket with static website content, configure CloudFront with OAI, add a custom domain via Route 53, enable HTTPS with ACM certificate, set up WAF with rate limiting and IP blocking, configure API behavior to forward to ALB, and optimize caching with versioned static assets.
FAQ
What’s Next
| Topic | Description |
|---|---|
| Running containers at scale | |
| Managed Kubernetes on AWS |
Related topics: AWS, S3, CloudFront
What’s Next
Congratulations on completing this CloudFront tutorial! Here’s where to go from here:
- Practice daily — Review your current CDN setup for optimization
- Build a project — Deploy a static site behind CloudFront with HTTPS
- Explore related topics — Check out ECS and EKS for container orchestration
Remember: every expert was once a beginner. Keep coding!
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro