Mid-level 6 min · March 06, 2026

CloudFront Cache — Why Deploys Never Reach Users

Old app.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • CloudFront caches at 450+ edge locations, serving content from nearest edge. Route 53 provides latency-based, geolocation, and failover DNS routing.
  • Together: Route 53 sends users to closest CloudFront edge; CloudFront serves cached content, falling back to origin only on cache miss.
  • Performance impact: 100ms latency reduction improves conversion by 1% (Amazon internal data). Global CDN cuts load time from 2.5s to 400ms.
  • Production trap: Cache invalidation costs money and takes minutes. Versioned filenames (app.abc123.js) are free and instant.
  • Hidden killer: ACM certificates for CloudFront MUST be in us-east-1. Cert in eu-west-1 = distribution fails to create.
  • Biggest mistake: Forgetting health checks on Route 53 failover records. Without them, primary fails, traffic never switches.
Plain-English First

Imagine your website is a pizza restaurant with one kitchen in New York. If someone in Tokyo orders a pizza, it takes forever to arrive. CloudFront is like opening pizza pickup counters all over the world — customers grab a hot slice from the nearest counter instead of waiting for New York. Route 53 is the smart GPS that tells every customer exactly which counter is closest to them. Together, they make sure your website loads fast no matter where on Earth your user is sitting.

Every second of load time costs you users. Amazon's own research found that a 100ms delay reduces sales by 1%. If your app is hosted in a single AWS region — say us-east-1 — every user outside the US East Coast is experiencing that penalty multiplied.

CloudFront and Route 53 solve two distinct problems. CloudFront is a CDN — it caches content at over 450 edge locations so users never have to reach all the way back to your origin. Route 53 is DNS — it translates domain names to IP addresses, but goes far beyond basic DNS with latency-based, geolocation, and failover routing.

This article covers the three rules that make CDN+Route53 work in production: versioned filenames over invalidation, alias records over CNAMEs, and always attaching health checks to failover records. Miss a rule and your users see stale content or your failover never triggers.

CloudFront + Route 53 Integration — The Global Delivery Stack

CloudFront and Route 53 solve different problems. CloudFront is a CDN — it caches content at edge locations and serves users from the nearest edge. Route 53 is DNS — it translates domain names to IP addresses, but AWS's version goes far beyond basic DNS with latency-based, geolocation, weighted, and failover routing.

Together, they form the global delivery stack: Route 53 directs the user's DNS query to the closest CloudFront edge (latency-based routing). The user's browser then connects to that CloudFront edge. CloudFront serves the content from its cache if available; otherwise, it fetches from the origin (S3, EC2, ALB, or custom HTTP server).

Why this matters: A SaaS company hosted only in us-east-1 with no CloudFront saw 2.5s load times in Australia. After adding CloudFront with a simple cache-all policy, load times dropped to 400ms. That's not theoretical — it's the kind of improvement that directly impacts revenue.

The real value isn't just speed. It's resilience. When your origin goes down in us-east-1, CloudFront can still serve stale content from its cache for hours. That's the difference between a 5-minute outage and a non-event. Route 53's health checks then detect the failure and reroute new requests to a replica in another region.

io/thecodeforge/cloudfront_invalidation.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!/bin/bash
# io.thecodeforge/cloudfront_invalidation.sh
# Create CloudFront invalidation for a distribution

DISTRIBUTION_ID="E1ABC123XYZ"

# Invalidate entire distribution (use carefully in production)
echo "Creating invalidation for distribution: ${DISTRIBUTION_ID}"
aws cloudfront create-invalidation \
  --distribution-id "${DISTRIBUTION_ID}" \
  --paths '/*'

# Invalidate specific files or directories
aws cloudfront create-invalidation \
  --distribution-id "${DISTRIBUTION_ID}" \
  --paths '/index.html' '/static/css/*' '/static/js/app.js'

# Check cache hit ratio from CloudWatch
aws cloudwatch get-metric-statistics \
  --namespace AWS/CloudFront \
  --metric-name CacheHitRate \
  --statistics Average \
  --start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) \
  --period 3600 \
  --dimensions Name=DistributionId,Value="${DISTRIBUTION_ID}"

echo "Invalidation created. Use 'aws cloudfront get-invalidation --id <ID>' to track progress."
Output
Invalidation created: I123ABC
Status: InProgress
Cache hit ratio (last hour): 94.2%
Forge Tip: Use Versioned Filenames to Avoid Invalidation
Invalidation takes minutes and costs per path. Versioned filenames (app.abc123.js) create a new URL on each deploy — cache miss is automatic. Set long TTL (1 year) and never invalidate. This is the industry standard for static assets.
Production Insight
A team wasted $500/month on CloudFront invalidations because they invalidated the entire distribution (/*) after every deploy.
100 deploys × $0.005 per path × thousands of files = large bill.
Fix: switched to versioned filenames. New file → new URL → no invalidation needed.
Rule: invalidation is for emergencies and dynamic content only. For static assets, versioning is free and instant.
Key Takeaway
CloudFront = CDN (edge caching). Route 53 = DNS (smart routing).
Together they form the global delivery stack: Route 53 routes to nearest edge, CloudFront serves cached content.
Versioned filenames > invalidation for static assets. Invalidation is for emergencies.
Target >90% cache hit ratio for static content. Low ratio means cache key or TTL problems.
Invalidation vs Versioned Filenames Decision Guide
IfStatic assets (JS, CSS, images) that change with each deploy
UseUse versioned filenames. Generate content hash (e.g., app.abc123.js). Set TTL to 1 year. No invalidation needed.
IfDynamic HTML pages (index.html) that reference versioned assets
UseSet short TTL (60s) on HTML. When HTML changes, CloudFront picks up new version within 60s. No invalidation needed.
IfAPI responses that return 404 after you add a new endpoint
UseInvalidation for specific path ('/api/v2/users'). Only for urgent fixes or new endpoints. Default TTL can be 60s for APIs.
IfEmergency security patch — need to remove cached content immediately
UseUse invalidation with specific paths ('/malicious.js'). Wait for completion (2-15 minutes). Also rotate any security tokens.

Route 53 Routing Policies: Latency, Geolocation, and Failover

Route 53 offers several routing policies that go far beyond simple DNS resolution.

Latency-based routing: Route 53 measures round-trip time between the user's resolver and each AWS region where you have an endpoint. It returns the IP of the region with the lowest latency. Ideal for active-active setups where you want automatic performance optimisation.

Geolocation routing: Routes users based on their geographic origin (continent, country, or state). Useful for compliance (GDPR privacy notice for EU users) or content localization (different pricing per country). Note: geolocation uses requestor's IP, not DNS resolver location.

Weighted routing: Distributes traffic across multiple endpoints based on assigned weights. Great for canary deployments (1% traffic to new version) or A/B testing.

Failover routing: Works with Route 53 health checks. If the primary endpoint fails health check, Route 53 returns the secondary endpoint's IP. Ensure health check intervals are short enough for your desired RTO.

Production nuance: DNS caching at ISPs can override Route 53 decisions. Set TTL low (60s) for latency and failover records to respond quickly to network changes.

Another nuance: latency-based routing works at the resolver level, not the end user. If a user's corporate DNS resolver is in Virginia but the user is in Europe, they get routed based on Virginia's latency. That's why geolocation can be more predictable for compliance use cases.

route53-failover-record.jsonJSON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
{
  "Changes": [
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "api.yourapp.com.",
        "Type": "A",
        "SetIdentifier": "primary-us-east-1",
        "Failover": "PRIMARY",
        "AliasTarget": {
          "HostedZoneId": "Z2FDTNDATAQYW2",
          "DNSName": "d123.cloudfront.net.",
          "EvaluateTargetHealth": true
        }
      }
    },
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "api.yourapp.com.",
        "Type": "A",
        "SetIdentifier": "secondary-eu-west-1",
        "Failover": "SECONDARY",
        "AliasTarget": {
          "HostedZoneId": "Z2FDTNDATAQYW2",
          "DNSName": "d456.cloudfront.net.",
          "EvaluateTargetHealth": true
        }
      }
    }
  ]
}
Output
Created primary failover record pointing to us-east-1 CloudFront distribution.
Created secondary failover record pointing to eu-west-1 CloudFront distribution.
Health checks must be attached to primary record for failover to work.
Mental Model: DNS as Traffic Director
  • Latency routing is reactive: uses actual network measurements to find fastest region.
  • Geolocation is deterministic: based on IP address ranges, not real-time latency.
  • Failover routing requires health checks on EVERY endpoint — without them, failover never triggers.
  • Alias records (pointing to AWS resources) are free and support health evaluation.
  • Weighted routing is perfect for canary deployments: shift 1% traffic, monitor, then ramp up.
Production Insight
A large e-commerce site used geolocation routing for EU users but forgot that some ISPs use DNS forwarders in different countries.
Users in Germany got routed to the US because their corporate DNS resolver was in Virginia.
Rule: latency-based routing is more robust than geolocation for performance. Use geolocation only for legal restrictions.
Another failure: a team set failover routing but 'EvaluateTargetHealth' was set to 'No' on the alias record. The health check worked, but Route 53 ignored it. Always set EvaluateTargetHealth=true for alias records in failover configurations.
Test failover by actually taking the primary endpoint offline — not just in console.
Key Takeaway
Choose routing policy based on your actual failure mode: latency for speed, geolocation for rules, failover for availability.
Rule: always pair failover routing with health checks and EvaluateTargetHealth=true — without them, it's just fancy DNS that never switches.
Set TTL to 60s for routing records to allow quick adjustments.
Test failover in staging: simulate primary outage and measure time to switch.

Custom Domain Setup: SSL Certificates, Regions, and Aliases

To serve your app via CloudFront with a custom domain, you need three pieces that must match exactly.

SSL/TLS certificate must be in ACM (AWS Certificate Manager) in the us-east-1 region — even if your origin is elsewhere. CloudFront operates globally but validates certificates from us-east-1 only. The certificate must include all alternate domain names you plan to use (e.g., example.com and www.example.com).

CloudFront distribution must have the custom domain listed in Alternate Domain Names (CNAMEs). This tells CloudFront which domain names to accept traffic for and which certificate to use.

Route 53 alias record pointing to the CloudFront distribution's DNS name (e.g., d123.cloudfront.net). Alias records are free, work at the zone apex, and support health evaluation.

The trap that catches everyone: ACM certificate in eu-west-1 (where your EC2 is) works for ALB but fails for CloudFront. The error message is cryptic: 'The specified SSL certificate doesn't exist, isn't in us-east-1, or doesn't match the alternate domain names'. Always request CloudFront certificates in us-east-1.

Also: if you change the CloudFront distribution (e.g., add a new origin), edge locations can take 10-20 minutes to propagate the configuration. Plan a maintenance window for changes that affect routing.

cloudfront-custom-domain.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/bin/bash
# Step 1: Request certificate in us-east-1 (NOT your origin region)
aws acm request-certificate \
  --domain-name example.com \
  --subject-alternative-names www.example.com \
  --validation-method DNS \
  --region us-east-1

# Step 2: Create CloudFront distribution with custom domain
aws cloudfront create-distribution \
  --origin-domain-name myapp.s3.amazonaws.com \
  --default-root-object index.html \
  --aliases 'www.example.com,example.com' \
  --viewer-certificate "{\"ACMCertificateArn\":\"arn:aws:acm:us-east-1:123456789:certificate/abc-123\",\"SSLSupportMethod\":\"sni-only\",\"MinimumProtocolVersion\":\"TLSv1.2_2021\"}" \
  --default-cache-behavior '{"TargetOriginId":"myapp","ViewerProtocolPolicy":"redirect-to-https","Compress":true}'

# Step 3: Create Route 53 alias record
# Use the distribution domain name from the create-distribution output
# dXXX.cloudfront.net
aws route53 change-resource-record-sets --hosted-zone-id Z123456 --change-batch file://alias.json
Output
Certificate requested in us-east-1. Validate via DNS before use.
Distribution created: E1ABC123XYZ
Domain name: d1a2b3c4d5e6.cloudfront.net
Alias records created. Propagation may take 60 seconds due to TTL.
Certificate Region Trap — This Catches Everyone
If you request the certificate in any region other than us-east-1, CloudFront will refuse to use it. The error is not obvious: 'The specified SSL certificate doesn't exist, isn't in us-east-1, or doesn't match the alternate domain names'. It's in us-east-1, but your cert is in eu-west-1. Request all CloudFront certificates in us-east-1, regardless of where your origin lives.
Production Insight
Teams often miss that ACM certificates for CloudFront must be in us-east-1. They request the cert in the same region as their EC2 instance (eu-west-1, ap-southeast-2), then spend hours debugging SSL handshake failures.
Rule: always request CloudFront certificates in us-east-1, regardless of where your origin lives. Save the certificate ARN and use it consistently.
Another common mistake: after updating the CloudFront distribution (e.g., adding a new origin), the edge locations can take up to 10 minutes to propagate the configuration. During that window, users may see 502 errors or old origins. Plan a maintenance window for such changes.
When you change the distribution's origin, coordinate with DNS TTL to avoid serving old config.
Key Takeaway
Custom domain setup requires three things: certificate (us-east-1), CloudFront domain alias, and Route 53 alias record.
The most common failure is certificate region mismatch — always use us-east-1.
Alias records are free, support root domains (example.com), and evaluate health.
Test the domain with curl -I https://yourdomain.com before pointing production traffic.

Security: Origin Access Control, WAF, and Signed URLs

CloudFront provides several security layers beyond basic TLS.

Origin Access Control (OAC) — restricts access to your S3 origin so that only CloudFront can fetch objects. OAC replaces the older Origin Access Identity (OAI) and is strongly preferred for new distributions. You attach an OAC to the distribution's origin and update the bucket policy to allow requests signed by that OAC. OAC supports server-side encryption with KMS; OAI does not.

AWS WAF integration — associate a Web ACL with your CloudFront distribution to block malicious traffic (SQL injection, XSS, IP reputation lists, rate limiting) before it reaches your origin. WAF rules are applied at the edge, reducing origin load. WAF costs scale with request volume, so use rate-based rules to limit costs.

Signed URLs and Signed Cookies — for premium content, generate time-limited URLs that CloudFront verifies using a private key. Signed URLs are per-file; Signed Cookies apply to a set of files. Generate short expiration (minutes to hours) and combine with IP restrictions for defense in depth.

Field-Level Encryption — encrypts sensitive data (credit card numbers, PII) before sending to the origin, so even the origin cannot read the raw data. The origin receives only the encrypted value.

Real risk: If you use OAI instead of OAC, you cannot use AWS KMS to encrypt objects at rest in S3. Always choose OAC for new distributions.

cloudfront-oac-setup.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#!/bin/bash
# Create Origin Access Control (OAC) for S3 origin
# OAC replaces legacy Origin Access Identity (OAI)

# Step 1: Create OAC
OAC_ID=$(aws cloudfront create-origin-access-control \
  --origin-access-control-config "{
    \"Name\": \"MyAppOAC\",
    \"Description\": \"OAC for myapp S3 bucket\",
    \"SigningProtocol\": \"sigv4\",
    \"SigningBehavior\": \"always\",
    \"OriginAccessControlOriginType\": \"s3\"
  }" \
  --query 'OriginAccessControl.Id' \
  --output text)

echo "Created OAC: ${OAC_ID}"

# Step 2: Update bucket policy to allow only CloudFront with this OAC
# The policy condition checks for 'aws:SourceArn' matching your distribution
cat > bucket-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "cloudfront.amazonaws.com"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::myapp-bucket/*",
      "Condition": {
        "StringEquals": {
          "aws:SourceArn": "arn:aws:cloudfront::123456789012:distribution/E1ABC123XYZ"
        }
      }
    }
  ]
}
EOF

echo "Apply bucket policy: aws s3api put-bucket-policy --bucket myapp-bucket --policy file://bucket-policy.json"

# Step 3: Attach OAC to distribution origin
# aws cloudfront update-distribution --id E1ABC123XYZ --distribution-config file://dist-config.json
Output
Created OAC: MY-OAC-ID-12345
Bucket policy created. Apply to S3 bucket.
Distribution update required to attach OAC to origin.
Forge Tip: OAC vs OAI — Always Use OAC for New Distributions
OAC is the modern replacement for OAI. It supports KMS-encrypted S3 objects, works with Lambda@Edge, and provides better auditability. OAI still works but is considered legacy. Use OAC for any new CloudFront + S3 setup.
Production Insight
A media company used signed URLs but set the expiration to 10 years — attackers extracted the URL once and had permanent access.
They also forgot to restrict access by IP range in WAF.
Rule: signed URLs should have short expiration (minutes to hours). Combine with IP restriction for defense in depth.
Another real incident: a distribution with OAI was accidentally made public via bucket policy allowing 's3:GetObject' to everyone. OAC with source ARN condition prevents this misconfiguration — the bucket can only be read by the specific CloudFront distribution, not just any IAM principal.
One more: if you use field-level encryption, remember that the origin never sees the encrypted fields — your backend code will receive the raw data only if you decrypt at the edge. Essential for PCI compliance.
Key Takeaway
Security at the edge prevents attacks from reaching your origin.
Use OAC for S3 origins (supports KMS), WAF for common threats (rate limiting, SQLi, XSS).
Signed URLs must have short expiration (minutes to hours) and IP restrictions.
Never rely on security through obscurity — bucket policies and WAF are the bare minimum.

Production Performance: Cache Hit Ratio, Origin Shield, and Cost Optimisation

CloudFront's effectiveness is measured by cache hit ratio — percentage of requests served from edge cache rather than origin. Target >90% for static assets. Low hit ratio means your cache key is too specific (forwarding unique headers or query strings) or TTL is too short.

Origin Shield — an additional caching layer in a single region that sits between edges and origin. It aggregates requests from all edges, reducing connections to your origin. Enable it for origins that are not already behind a CDN and for high-traffic workloads. Origin Shield can dramatically reduce origin load during traffic spikes.

Regional Edge Caches — automatically enabled for all distributions (as of 2024+). They sit between edge locations and origin, caching content not requested frequently enough to stay in edge caches. No extra cost.

Cost optimisation: CloudFront charges per request and per GB transferred. Use compression (gzip/brotli) to reduce bytes. Brotli compresses text 20-25% better than gzip. Set appropriate TTLs to avoid frequent origin fetches. Use cost allocation tags to track spend per distribution.

Monitoring: CloudWatch metrics include Requests, BytesDownloaded, TotalErrorRate, and CacheHitRate. Set alarms for error rate >1% and cache hit rate <80%. Monitor OriginLatency for signs of origin overload.

Pricing nuance: CloudFront has free tier (1TB transfer/month), but costs can spike if you serve large files (video) or have high request rates (many small files). For small files, per-request cost dominates; consider combining files into archives or using HTTP/2 multiplexing.

cloudfront-performance-checks.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#!/bin/bash
DISTRIBUTION_ID="E1ABC123XYZ"

echo "=== CloudFront Performance Metrics ==="

# Cache hit ratio (last hour)
aws cloudwatch get-metric-statistics \
  --namespace AWS/CloudFront \
  --metric-name CacheHitRate \
  --statistics Average \
  --start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) \
  --period 3600 \
  --dimensions Name=DistributionId,Value="${DISTRIBUTION_ID}"

# Error rate
aws cloudwatch get-metric-statistics \
  --namespace AWS/CloudFront \
  --metric-name TotalErrorRate \
  --statistics Average \
  --start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) \
  --period 3600 \
  --dimensions Name=DistributionId,Value="${DISTRIBUTION_ID}"

# Origin latency (p95)
aws cloudwatch get-metric-statistics \
  --namespace AWS/CloudFront \
  --metric-name OriginLatency \
  --statistics p95 \
  --start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) \
  --period 3600 \
  --dimensions Name=DistributionId,Value="${DISTRIBUTION_ID}"

echo "=== Cache Hit Ratio Target: >90% for static content ==="
echo "=== Error Rate Target: <1% ==="
Output
CacheHitRate: 94.2% (OK)
TotalErrorRate: 0.3% (OK)
OriginLatency p95: 45ms (OK)
Cost vs Performance Balance
Don't set TTL to 0 for everything 'to be safe' — every request hits origin. Use versioned filenames and long TTLs for static assets. It's the cheapest way to improve performance and reduce origin costs simultaneously.
Production Insight
A startup saw their CloudFront bill triple after a viral post. They had no compression and TTLs set to 0 for all paths.
Every request hit the origin, which also meant high EC2 data transfer costs.
Fix: enabled Brotli compression (25% bandwidth reduction), set minimum TTL of 1 hour for static assets, enabled Origin Shield.
Bill dropped 70%, page load times improved 40%.
Rule: always review CloudFront cost and usage report before and after any launch event.
Another overlooked cost: invalidations cost money (first 1,000 paths per month free, then $0.005 per path). If you invalidate 10,000 paths daily, that's $1,500/month. Use versioned filenames instead.
Set a CloudWatch alarm on Requests to detect unexpected traffic spikes.
Key Takeaway
Target cache hit ratio >90% for static content. Low ratio = cache key or TTL problem.
Origin Shield reduces origin load during traffic spikes. Enable for high-volume workloads.
Compression: Brotli > gzip. Reduces bandwidth 20-25% for text content.
Cost monitoring: set CloudFront budget alerts. Invalidations cost money — use versioned filenames.
● Production incidentPOST-MORTEMseverity: high

The Deploy That Never Reached Users — Cache Invalidation Missed

Symptom
Users reporting that the new button/layout isn't visible. curl -I shows x-cache: Hit from cloudfront. Origin S3 bucket has the updated files. TTL is 24 hours.
Assumption
Engineers assumed CloudFront would automatically pick up new files because the S3 object keys remained the same (overwritten). They didn't know CloudFront ignores changes to existing keys.
Root cause
CloudFront caches objects by URL (including query strings and headers). When object content changes but the URL stays the same (e.g., overwriting app.js), the old cache persists until TTL expires. Default TTL is 24 hours. Invalidating /app.js would have fixed it, but the team didn't include the invalidation step in their deployment pipeline. The cache didn't expire until the next day's deploy, which happened to include a different filename anyway.
Fix
1. Added invalidation to deployment pipeline: aws cloudfront create-invalidation --distribution-id XYZ --paths /app.js /styles.css 2. Switched to versioned filenames: app.abc123.js generated from content hash. New file → new URL → cache miss automatically. 3. Set short TTL (60s) for HTML files that reference versioned assets. 4. Added monitoring: alert if x-cache header shows 'Hit' for updated file patterns after deploy. 5. For emergency fixes: create invalidation for exact path, wait for completion, verify via curl.
Key lesson
  • Never rely on TTL expiry for urgent content updates. Invalidation is required.
  • Versioned filenames (app.abc123.js) are free, instant, and the industry standard.
  • Include cache invalidation as a mandatory step in your deployment pipeline.
  • Use curl -I to check x-cache header: 'Miss' means origin fetch, 'Hit' means from edge.
  • For static sites, set short TTL (60s) on HTML, long TTL (1 year) on versioned assets.
Production debug guideSymptom → Action grid for the three most common production issues5 entries
Symptom · 01
CloudFront returns HTTP 403 Forbidden for S3 origin
Fix
Check Origin Access Control (OAC) settings. Ensure CloudFront has permission to read from the S3 bucket. Update bucket policy to allow aws:SourceArn = distribution ARN. OAC replaces legacy OAI.
Symptom · 02
DNS resolution fails after updating Route 53 record
Fix
Check TTL on the old record (default 300s). Use dig +trace yourdomain.com to see propagation. For planned changes, lower TTL to 60s 24 hours before the change.
Symptom · 03
High origin load despite CloudFront being enabled
Fix
Review CloudFront cache hit ratio in CloudWatch (target >90% for static). Check cache key: are you forwarding query strings, cookies, or headers unnecessarily? Each unique combination creates new cache entry.
Symptom · 04
Custom domain SSL not working — ERR_CERT_COMMON_NAME_INVALID
Fix
Verify ACM certificate is in us-east-1 region. Check Alternate Domain Name in CloudFront exactly matches certificate domain. Ensure Route 53 alias record points to CloudFront DNS name, not an IP.
Symptom · 05
Route 53 failover not triggering — primary down but traffic keeps going
Fix
Check that health check is attached to primary record ('Evaluate Target Health' = 'Yes' for aliases). Verify health check URL returns 200 within 2 seconds. Check if EvaluateTargetHealth is enabled.
★ CloudFront & Route 53 Quick Debug Cheat SheetCommands and actions for the most common production issues
User sees old content after deployment — check cache headers
Immediate action
Invalidate the CloudFront cache or use versioned filenames
Commands
curl -I https://yourdomain.com/app.js | grep -i x-cache
aws cloudfront create-invalidation --distribution-id YOUR_DIST_ID --paths '/*'
Fix now
Run invalidation for changed paths; for static assets, implement versioned filenames in build process.
Route 53 failover not triggering — check health check and record config+
Immediate action
Verify health check status and record evaluation
Commands
aws route53 test-dns-answer --hosted-zone-id ZONE_ID --record-name yourdomain.com --record-type A
aws route53 get-health-check-status --health-check-id HC_ID
Fix now
Attach health check to primary record, set EvaluateTargetHealth=true for alias records, ensure health check URL returns 200.
DNS not resolving to CloudFront — check alias record+
Immediate action
Verify Route 53 alias points to correct distribution DNS name
Commands
dig +short yourdomain.com
aws route53 list-resource-record-sets --hosted-zone-id ZONE_ID --query "ResourceRecordSets[?Name=='yourdomain.com.']"
Fix now
Update alias target to correct distribution DNS name (dXXX.cloudfront.net). Wait for TTL expiry.
Certificate error — check region and domain match+
Immediate action
Verify ACM certificate is in us-east-1 and covers Alternate Domain Names
Commands
aws acm list-certificates --region us-east-1 --query "CertificateSummaryList[?DomainName=='example.com']"
aws cloudfront get-distribution --id DIST_ID --query "Distribution.DistributionConfig.Aliases"
Fix now
Request certificate in us-east-1. Add all alternate domain names to certificate and CloudFront alias list.
WAF blocking legitimate traffic — check sampled requests+
Immediate action
Examine WAF logs in CloudWatch, identify blocking rule
Commands
aws wafv2 get-sampled-requests --web-acl-arn YOUR_ACL_ARN --rule-metric-name YOUR_RULE --scope CLOUDFRONT --time-window Start=...,End=...
aws wafv2 update-rule-group --name YOUR_GROUP --scope CLOUDFRONT --lock-token TOKEN --rules file://updated-rules.json
Fix now
Switch blocking rule to COUNT mode temporarily, adjust rate limit or scope, then re-enable BLOCK.
CloudFront vs Route 53 — Complementary Roles
AspectCloudFrontRoute 53
Primary roleContent delivery (CDN) — cache and serve at edgeDNS resolution — route users to best endpoint
Latency reductionServes cached content from nearest edge locationRoutes to region with lowest network latency via latency-based routing
CachingYes — TTL-based, configurable cache keys (URL, query string, headers)No caching — only DNS response caching (TTL on records)
Failover capabilityOnly via integration with Route 53 or origin failover groupsNative failover routing with health checks and EvaluateTargetHealth
Security featuresWAF, OAC (Origin Access Control), signed URLs, field-level encryptionDNSSEC, traffic flow policies, resolver-level filtering
Cost modelPer request + per GB data transfer. Caching reduces cost. Free tier: 1TB/month.Per hosted zone ($0.50/month) + per million queries ($0.40/million). Alias records free.
Deployment timeDistribution creation: 10-20 minutes. Invalidation: 2-15 minutes.DNS record changes: seconds, but propagation depends on TTL (default 300s)

Key takeaways

1
CloudFront and Route 53 are complementary
CloudFront caches content, Route 53 routes users. Use both for global applications.
2
Versioned filenames > invalidation for static assets. Set TTL to 1 year, never invalidate. Invalidation is for emergencies only.
3
Route 53 failover requires health checks and EvaluateTargetHealth=true. Without them, failover never triggers.
4
ACM certificates for CloudFront MUST be in us-east-1. Cert in any other region = distribution creation fails.
5
Alias records are free, support root domains, and evaluate health. CNAME records cannot be used at zone apex.
6
Target cache hit ratio >90% for static content. Low ratio = cache key too specific (forwarding unique query strings).
7
Enable Brotli compression. Reduces bandwidth 20-25% for text content. Both gzip and Brotli supported.
8
Use OAC (Origin Access Control) for S3 origins. OAC supports KMS encryption; legacy OAI does not.
9
Set CloudFront cost budget alarms. Invalidations cost money. Request counts scale with your traffic.
10
Test failover by actually taking the primary endpoint offline. Simulate real failures, not just console checks.
11
Use wildcard invalidation paths (/static/*) for batch updates. Individual paths cost more and take longer.

Common mistakes to avoid

7 patterns
×

Setting TTLs too low or using invalidation for every deploy

Symptom
High origin load, increased latency, higher CloudFront bill (more origin fetches). Invalidations cost money and take minutes.
Fix
Use versioned filenames for static assets (app.abc123.js). Set TTL to 1 year. For HTML files referencing versioned assets, set short TTL (60s). Reserve invalidation for emergencies.
×

Forgetting to attach health checks to Route 53 failover records

Symptom
Primary endpoint fails, but Route 53 continues returning its IP. Users get errors, failover never triggers.
Fix
Attach health check to primary record. For alias records, set EvaluateTargetHealth=true. Verify health check URL returns 200 within 2 seconds. Test failover by taking primary offline.
×

Requesting ACM certificate outside us-east-1 for CloudFront

Symptom
CloudFront distribution creation fails with 'The specified SSL certificate doesn't exist, isn't in us-east-1, or doesn't match the alternate domain names'.
Fix
Always request CloudFront certificates in us-east-1. Save the ARN. Use the same region for certificate, regardless of origin region. The error message is correct — the certificate technically doesn't exist in us-east-1.
×

Using CNAME instead of Alias record for root domain (example.com)

Symptom
DNS resolution fails for root domain — DNS protocol forbids CNAME at zone apex. Browser shows 'domain not found'.
Fix
Use Route 53 alias record for root domain. Alias records are free, work at apex, and support health evaluation. For subdomains (www), alias records are also preferred but CNAME works.
×

Not enabling compression on CloudFront distributions

Symptom
High data transfer costs, slow loading for text-based content (HTML, CSS, JS, JSON). Brotli can reduce size 20-25%.
Fix
Enable compression in cache behavior. Set 'CompressObjects' to true. Both gzip and Brotli are supported for appropriate content types.
×

Forwarding all query strings in cache key

Symptom
Cache hit ratio below 20%. Each unique query string (e.g., ?session=abc) creates new cache entry, preventing cache reuse.
Fix
Configure cache policy to forward only necessary query strings (e.g., ?lang=en). For A/B testing, forward only the A/B parameter. For versioning, use URL path, not query string.
×

Using OAI (Origin Access Identity) instead of OAC for KMS-encrypted buckets

Symptom
CloudFront can't read objects from S3 bucket encrypted with KMS. OAI does not support KMS.
Fix
Use OAC (Origin Access Control) instead. OAC supports KMS encryption and provides better auditability through source ARN conditions. Migrate existing OAI distributions to OAC.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
Explain the difference between CloudFront cache invalidation and object ...
Q02SENIOR
How would you design a global application that serves both static conten...
Q03JUNIOR
What happens when you create a CloudFront distribution with a custom dom...
Q04SENIOR
Describe a real situation where a weighted Route 53 routing policy cause...
Q05SENIOR
How do you handle cache invalidation for a large deployment with thousan...
Q06SENIOR
When would you use geolocation routing over latency-based routing in Rou...
Q01 of 06SENIOR

Explain the difference between CloudFront cache invalidation and object versioning. Which would you use and why?

ANSWER
Invalidation removes cached objects by path from all edge locations, which takes 2-15 minutes and costs $0.005 per path after the first 1,000 paths per month. Versioning appends a unique hash to the filename (e.g., app.abc123.js), so a new URL is created each deployment — no invalidation needed. I prefer versioning for static assets (JS, CSS, images) because it's instant, free at scale, and avoids stale content during invalidation propagation. For dynamic content that cannot change URL (e.g., index.html), I use short TTL (60s) and optional invalidation for emergency fixes. Real example: a team invalidating 1,000 paths daily pays $150/month. Versioned filenames cost $0 and provide better performance.
FAQ · 11 QUESTIONS

Frequently Asked Questions

01
What is the difference between CloudFront cache invalidation and object versioning?
02
Why does my CloudFront distribution fail to create with 'The specified SSL certificate doesn't exist, isn't in us-east-1, or doesn't match the alternate domain names'?
03
Can I use CloudFront without Route 53?
04
What is the difference between Origin Shield and Regional Edge Caches?
05
How long does CloudFront cache invalidation take?
06
What health check does Route 53 use for failover?
07
Why is my CloudFront distribution showing 502 errors after updating the origin?
08
Can I use CloudFront with a non-AWS origin?
09
How do I set up a custom error response in CloudFront?
10
What is the difference between 'CNAME' and 'Alias' records in Route 53?
11
How do I reduce CloudFront costs for a high-traffic website?
🔥

That's Cloud. Mark it forged?

6 min read · try the examples if you haven't

Previous
AWS IAM — Identity and Access
9 / 23 · Cloud
Next
Introduction to Google Cloud Platform