CloudFront Cache — Why Deploys Never Reach Users
Old app.js served for 24 hours after deploy because cache TTL hadn't expired.
20+ years shipping production infrastructure and CI/CD at scale. Everything here is grounded in real deployments.
- ✓Solid grasp of DevOps fundamentals
- ✓Comfortable with command-line tools
- ✓Basic Linux administration knowledge
- 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.
CloudFront is AWS's content delivery network (CDN) that caches static and dynamic content at 600+ edge locations globally. Route 53 is AWS's DNS service that routes users to the nearest CloudFront distribution via latency-based or geolocation routing.
Together, they form the backbone of high-performance web delivery on AWS. The problem they solve is simple: without a CDN, every user request hits your origin server directly, causing high latency, origin load, and single points of failure. CloudFront absorbs traffic at the edge, while Route 53 ensures users reach the optimal edge endpoint.
The catch—and the reason deploys 'never reach users'—is that CloudFront caches aggressively by default. When you deploy new code or assets, users still see the old cached version until the TTL expires or you manually invalidate the cache. This is the single most common production surprise for teams new to AWS.
Alternatives exist: you could skip the CDN and use Route 53 directly with ALB or EC2, but you'd lose global performance and DDoS protection. For low-latency, high-traffic sites, CloudFront + Route 53 is the standard. For internal apps or low-traffic APIs, it's overkill—use CloudFront only when you need edge caching, TLS termination at the edge, or WAF integration.
Real-world numbers: a properly configured CloudFront distribution can achieve 95%+ cache hit ratios, reducing origin load by orders of magnitude and cutting latency from 200ms to under 20ms for distant users. The trade-off is cache invalidation cost—$0.005 per path per month, which adds up if you invalidate aggressively.
Production teams typically set TTLs to 24 hours for stable assets and use versioned filenames (e.g., app.abc123.js) to avoid invalidation entirely.
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.
| Chrome | Firefox | Safari | Edge |
|---|---|---|---|
| ✓ | ✓ | ✓ | ✓ |
Developers love the shiny new feature. They hate the 12-second DNS propagation, the SSL mismatch errors, and the fact that their global users see a 5-second load time because CloudFront skipped Route 53’s routing policy. Without a tight CloudFront and Route 53 handshake, your deployment is a gamble—users get routed to the wrong region, cache misses stack up, and origin traffic bleeds cash. This stack isn’t just about speed; it’s the critical bridge between DNS resolution and edge distribution that, when configured wrong, silently kills your user experience and your budget.
Why CloudFront + Route53 Means Your Deploy Never Reaches Users
CloudFront is a content delivery network (CDN) that caches responses at edge locations worldwide. Route53 is AWS's DNS service. Together, they form the front door of most production web applications. The core mechanic: Route53 resolves your domain to a CloudFront distribution, and CloudFront serves cached content from edge caches — meaning requests never hit your origin server unless the cache misses. This is great for latency and origin load, but catastrophic for deployments: after you push new code, users still see the old cached version until the TTL expires or you explicitly invalidate the cache. CloudFront caches based on the URL and query string by default — not on headers like version or ETag unless you configure it. The default TTL is 24 hours. That means a deploy at 10 AM won't be visible to most users until the next day. You must either set shorter TTLs (e.g., 1 minute) for dynamic paths or use cache invalidation (costs money per path) or versioned URLs (e.g., /static/v2/main.js). The practical takeaway: CloudFront is not transparent — it actively hides your changes from users until you tell it to stop.
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.
#!/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."
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.
{
"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
}
}
}
]
}- 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.
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.
#!/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
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.
#!/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
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.
#!/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% ==="
Requests to detect unexpected traffic spikes.What the Hell is Route53 Actually Doing?
Route53 is not magic. It's a DNS service with a global anycast network and a health-check engine strapped to the side. When a client hits your CloudFront distribution, Route53 translates the human-readable domain into an IP — but that's the boring part.
The real power is the latency-based routing. Route53 maintains a latency map between every AWS edge location and your origins. When a user in São Paulo requests your site, Route53 doesn't just serve any old IP. It looks up which CloudFront edge is closest to that user's resolver, returns that IP, and your content travels the shortest possible path. That's why your global users don't hate you.
But here's the trap: Route53's health checks are optional per record. If your CloudFront distribution goes down, Route53 will keep returning the same IP unless you configure failover routing with active health checks. Without them, you're serving dead endpoints. Production incident waiting to happen.
// io.thecodeforge — devops tutorial // This is the minimum health check config to avoid serving dead origins export const healthCheckConfig: AWS.Route53.Types.HealthCheckConfig = { Type: 'HTTPS', FullyQualifiedDomainName: 'api.production.myapp.com', Port: 443, ResourcePath: '/health', RequestInterval: 30, // seconds FailureThreshold: 3, EnableSNI: true, Regions: [ 'us-east-1', 'eu-west-1', 'ap-southeast-1' ] }; // Bind this to a failover record set. If health fails for 90s, Route53 flips to secondary.
Prerequisites — The Things You'll Hate Yourself For Skipping
Before you touch a single Route53 record, get these sorted. I've seen production deploys stall because some junior didn't have the domain transferred or forgot about DNS TTL propagation. Don't be that person.
You need: an active AWS account (obviously), a registered domain — either through Route53's registrar or a third-party like Namecheap. If it's third-party, make sure the nameservers are pointed to AWS. That means four NS records from your registrar pointing to the Route53 hosted zone's delegation set. Get this wrong and your domain won't resolve. Happens weekly.
DNS concepts aren't optional. You need to understand TTL, A/AAAA records, CNAME, and alias records. Especially alias records — they're Route53's killer feature for CloudFront. Unlike CNAME, alias records work at the zone apex (example.com, not www.example.com) and cost nothing extra. Use them.
Finally, have your SSL/TLS certificate ready — ideally in AWS Certificate Manager in us-east-1. CloudFront only loads certificates from us-east-1. Request it there, not in your app's region. That mistake has cost teams days of debugging.
#!/bin/bash # io.thecodeforge — devops tutorial # Verify your domain's nameservers point to AWS Route53 dig NS example.com +short # Expected output: # ns-1234.awsdns-56.org. # ns-5678.awsdns-78.co.uk. # ns-9012.awsdns-34.com. # ns-3456.awsdns-12.net. # If you see anything else, fix your registrar config before proceeding. # Check that your ACM certificate is in us-east-1 aws acm list-certificates --region us-east-1 --query "CertificateSummaryList[?DomainName=='example.com']"
Gradual Cutover Strategy: DNS Weighting and Dual-Origin Migration
Changing DNS records for a production CloudFront distribution is risky: TTLs are cached globally, and a single misconfigured origin can take down all users before you notice. A gradual cutover avoids this. The why is simple: Route53 lets you attach multiple weighted records to the same alias, splitting traffic between your old origin and the new CloudFront distribution. Start with a 99/1 split, incrementally shift the weight over hours or days while monitoring error rates, cache hit ratios, and latency. This exposes problems in the new origin — wrong S3 bucket policies, stale SSL, or missing CORS headers — before they affect everyone. Route53 health checks can also automatically fail back to the old origin if the new target returns 5xx. Once 100% traffic is stable on CloudFront, delete the old record. This strategy is mandatory for any migration that cannot tolerate downtime or a full rollback.
// io.thecodeforge — devops tutorial // Gradual cutover: 10% new CloudFront, 90% old origin AWSTemplateFormatVersion: '2010-09-09' Resources: OldOriginRecord: Type: AWS::Route53::RecordSet Properties: Name: www.example.com Type: A AliasTarget: DNSName: old-alb-123.us-east-1.elb.amazonaws.com Weight: 90 NewCloudFrontRecord: Type: AWS::Route53::RecordSet Properties: Name: www.example.com Type: A AliasTarget: DNSName: d123.cloudfront.net Weight: 10 SetIdentifier: CloudFront
CloudFormation Infrastructure: Declarative CloudFront + Route53 Pipelines
Manually clicking through the AWS console to set up CloudFront and Route53 is fragile and unrepeatable. CloudFormation turns your entire global delivery stack into version-controlled infrastructure. The why is obvious: one template defines the origin, distribution, SSL certificate, aliases, and Route53 record in a single deploy. Changes are reviewed via pull requests, rolled back via change sets, and auditable in CloudTrail. The key pattern is the CloudFront distribution's DomainName output passed as an alias target to a Route53 RecordSet — both in the same stack. Include a Lambda@Edge associaton for signed URLs or header rewriting without leaving the template. Use nested stacks to separate the WAF, the origin bucket, and the distribution, so you can update security policies independently. This eliminates the most common production errors: mismatched SSL regions, forgotten Origin Access Control, and misconfigured CNAMEs.
// io.thecodeforge — devops tutorial AWSTemplateFormatVersion: '2010-09-09' Resources: CloudFrontDistribution: Type: AWS::CloudFront::Distribution Properties: DistributionConfig: Aliases: [www.example.com] ViewerCertificate: AcmCertificateArn: arn:aws:acm:us-east-1:123456:certificate/abc SslSupportMethod: sni-only Origins: - DomainName: my-bucket.s3.amazonaws.com Id: S3Origin S3OriginConfig: OriginAccessIdentity: origin-access-identity/cloudfront/E1A2B3C4D5 Enabled: true DnsRecord: Type: AWS::Route53::RecordSet Properties: Name: www.example.com Type: A AliasTarget: DNSName: !GetAtt CloudFrontDistribution.DomainName HostedZoneId: Z2FDTNDATAQYW2
Overview: Why CloudFront and Route53 Belong Together
Before you wire a CDN to a DNS provider, ask why they should be coupled at all. CloudFront gives you edge caching and DDoS protection. Route53 gives you global traffic routing. Alone, each solves a different problem. Together, they solve the hardest one: ensuring your users hit the nearest healthy endpoint without manual failover or stale caches. The real value isn't feature stacking—it's eliminating the gap between DNS resolution and content delivery. When Route53 routes a user to a CloudFront edge location, the distance between request and response drops from 200ms to 20ms. More importantly, Route53's health checks can tell CloudFront which origin is alive, so your CDN doesn't serve 502s from a dead server. This isn't about convenience; it's about preventing the scenario where your deployment works but users still see broken pages because DNS cached an old IP. Choose this stack when uptime and speed are non-negotiable, and you need one console to manage both.
// io.thecodeforge — devops tutorial // Overview: minimal Route53 + CloudFront alias AWSTemplateFormatVersion: '2010-09-09' Parameters: DomainName: Type: String Default: 'example.io' Resources: Distribution: Type: AWS::CloudFront::Distribution Properties: DistributionConfig: Aliases: [!Ref DomainName] DefaultCacheBehavior: TargetOriginId: myOrigin ViewerProtocolPolicy: redirect-to-https Origins: - DomainName: myorigin.com Id: myOrigin DNS: Type: AWS::Route53::RecordSetGroup Properties: HostedZoneName: !Sub '${DomainName}.' RecordSets: - Name: !Ref DomainName Type: A AliasTarget: DNSName: !GetAtt Distribution.DomainName HostedZoneId: Z2FDTNDATAQYW2
Architecture: How the Pieces Actually Fit Together
Stop thinking of CloudFront and Route53 as separate services. In production, they form a single traffic pipeline with three layers. First, Route53 receives a DNS query and applies a routing policy—latency-based, geolocation, or weighted. It resolves to the nearest CloudFront edge location. Second, that edge checks its cache. On a miss, it forwards to the origin through an Origin Access Control (OAC) or custom header. Third, Route53's health probes continuously monitor your origin endpoints and feed their status back to CloudFront via origin failover groups. The architectural trap most engineers fall into: they configure DNS routing to multiple origins but forget CloudFront can only talk to one origin per behavior. The fix is a CloudFront origin group with primary and secondary origins tied to Route53 health checks. This means if your primary origin in us-east-1 fails, the edge fetches from eu-west-1 without any DNS propagation delay. The result: sub-second failover instead of the 60-second TTL hell most teams accept. Always model the pipeline as DNS → edge cache → origin group → health feedback loop.
// io.thecodeforge — devops tutorial // Architecture: CloudFront origin group with failover AWSTemplateFormatVersion: '2010-09-09' Resources: Distribution: Type: AWS::CloudFront::Distribution Properties: DistributionConfig: Origins: - DomainName: primary.example.io Id: primaryOrigin CustomOriginConfig: OriginProtocolPolicy: https-only - DomainName: secondary.example.io Id: secondaryOrigin CustomOriginConfig: OriginProtocolPolicy: https-only OriginGroups: Items: - Id: failoverGroup FailoverCriteria: StatusCodes: [503, 504] Members: - OriginId: primaryOrigin - OriginId: secondaryOrigin DefaultCacheBehavior: TargetOriginId: failoverGroup ViewerProtocolPolicy: redirect-to-https
The Deploy That Never Reached Users — Cache Invalidation Missed
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.- 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.
aws:SourceArn = distribution ARN. OAC replaces legacy OAI.dig +trace yourdomain.com to see propagation. For planned changes, lower TTL to 60s 24 hours before the change.EvaluateTargetHealth is enabled.curl -I https://yourdomain.com/app.js | grep -i x-cacheaws cloudfront create-invalidation --distribution-id YOUR_DIST_ID --paths '/*'aws route53 test-dns-answer --hosted-zone-id ZONE_ID --record-name yourdomain.com --record-type Aaws route53 get-health-check-status --health-check-id HC_IDdig +short yourdomain.comaws route53 list-resource-record-sets --hosted-zone-id ZONE_ID --query "ResourceRecordSets[?Name=='yourdomain.com.']"aws acm list-certificates --region us-east-1 --query "CertificateSummaryList[?DomainName=='example.com']"aws cloudfront get-distribution --id DIST_ID --query "Distribution.DistributionConfig.Aliases"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| Aspect | CloudFront | Route 53 |
|---|---|---|
| Primary role | Content delivery (CDN) — cache and serve at edge | DNS resolution — route users to best endpoint |
| Latency reduction | Serves cached content from nearest edge location | Routes to region with lowest network latency via latency-based routing |
| Caching | Yes — TTL-based, configurable cache keys (URL, query string, headers) | No caching — only DNS response caching (TTL on records) |
| Failover capability | Only via integration with Route 53 or origin failover groups | Native failover routing with health checks and EvaluateTargetHealth |
| Security features | WAF, OAC (Origin Access Control), signed URLs, field-level encryption | DNSSEC, traffic flow policies, resolver-level filtering |
| Cost model | Per 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 time | Distribution creation: 10-20 minutes. Invalidation: 2-15 minutes. | DNS record changes: seconds, but propagation depends on TTL (default 300s) |
| File | Command / Code | Purpose |
|---|---|---|
| io | DISTRIBUTION_ID="E1ABC123XYZ" | CloudFront + Route 53 Integration |
| route53-failover-record.json | { | Route 53 Routing Policies |
| cloudfront-custom-domain.sh | aws acm request-certificate \ | Custom Domain Setup |
| cloudfront-oac-setup.sh | OAC_ID=$(aws cloudfront create-origin-access-control \ | Security |
| cloudfront-performance-checks.sh | DISTRIBUTION_ID="E1ABC123XYZ" | Production Performance |
| Route53HealthCheck.ts | export const healthCheckConfig: AWS.Route53.Types.HealthCheckConfig = { | What the Hell is Route53 Actually Doing? |
| PrerequisitesCheck.sh | dig NS example.com +short | Prerequisites |
| route53-weighted-migration.yml | AWSTemplateFormatVersion: '2010-09-09' | Gradual Cutover Strategy |
| cloudfront-route53-stack.yml | AWSTemplateFormatVersion: '2010-09-09' | CloudFormation Infrastructure |
| overview-minimal.yml | AWSTemplateFormatVersion: '2010-09-09' | Overview |
| architecture-group.yml | AWSTemplateFormatVersion: '2010-09-09' | Architecture |
Key takeaways
/static/*) for batch updates. Individual paths cost more and take longer.Common mistakes to avoid
7 patternsSetting TTLs too low or using invalidation for every deploy
Forgetting to attach health checks to Route 53 failover records
Requesting ACM certificate outside us-east-1 for CloudFront
Using CNAME instead of Alias record for root domain (example.com)
Not enabling compression on CloudFront distributions
Forwarding all query strings in cache key
Using OAI (Origin Access Identity) instead of OAC for KMS-encrypted buckets
Interview Questions on This Topic
Explain the difference between CloudFront cache invalidation and object versioning. Which would you use and why?
How would you design a global application that serves both static content (images, CSS) and dynamic API responses with low latency? Use CloudFront and Route 53 in your answer.
What happens when you create a CloudFront distribution with a custom domain but the ACM certificate is in the wrong region?
Describe a real situation where a weighted Route 53 routing policy caused a production incident, and how you diagnosed it.
dig to see which records were returned for different clients. The discrepancy showed both records had equal weight in Route 53's returned results.
Fix: updated weights to 90/10, tested with a small percentage first, and monitored CloudFront Requests metric per distribution. Now we also use canary deployments with the new version receiving 1% traffic initially, monitoring error rate before ramping up.
Lesson: always verify weight distribution by checking request counts, not just the configuration. Test weighted routing in staging before production.How do you handle cache invalidation for a large deployment with thousands of files?
/static/) costs $0.005 — one path. Using 10,000 individual paths costs $49.995 plus slow propagation. Always use wildcards.When would you use geolocation routing over latency-based routing in Route 53?
Frequently Asked Questions
Invalidation removes cached objects from all edge locations by path, taking 2-15 minutes and costing $0.005 per path after 1,000 free paths per month. Versioning changes the object's URL (e.g., app.abc123.js), so the old URL remains cached but new requests use the new URL. Prefer versioning for static assets — it's instant, free, and the industry standard.
CloudFront requires ACM certificates in the us-east-1 region. Your certificate is likely in a different region (e.g., eu-west-1). Request the certificate in us-east-1, regardless of where your origin lives. This is the most common CloudFront configuration error.
Yes, CloudFront distribution has its own DNS name (dXXX.cloudfront.net). You can point any DNS provider's CNAME record to that name. However, Route 53 alias records simplify root domain (example.com) support and provide free health evaluation. Route 53 also integrates latency-based routing to direct users to the nearest CloudFront edge.
Regional Edge Caches are automatically enabled and sit between edge locations and origin. They cache content not popular enough for edge caches. Origin Shield is optional and provides an additional caching layer in a single region, reducing origin load. Use Origin Shield when you have high traffic or want to protect a non-AWS origin from spikes. Both are free beyond standard CloudFront costs.
Invalidation typically completes in a few minutes for most edge locations, but can take up to 15 minutes for full propagation to all 450+ points of presence. The time depends on the path pattern (wildcards take longer), current load, and which edges need to remove the content. Check invalidation status using aws cloudfront get-invalidation --id INV_ID.
Route 53 health checks can be endpoint-based (HTTP/HTTPS/TCP to a specific IP or domain), calculated (combinations of other health checks), or CloudWatch metric-based (e.g., SQS queue depth, ALB target health). For HTTP, configure the path, interval (10-30 seconds), failure threshold (1-10 failures), and optional string matching in response body.
If you changed the origin's endpoint (e.g., new S3 bucket or new ALB), the CloudFront distribution may still be pointing to the old origin. Update the origin settings in the CloudFront console. Also verify that security groups and network ACLs allow CloudFront's edge IP ranges (AWS publishes them in ip-ranges.json).
Yes, CloudFront supports custom origins (any HTTP/HTTPS server). However, you lose tight integration like OAC (for S3) and automatic KMS support. You'll need to ensure the origin is reachable from all CloudFront edge locations (use firewalls to allow CloudFront IP ranges) and has valid TLS certificates. Consider using Origin Shield to protect a non-AWS origin from direct traffic spikes.
In the CloudFront distribution settings, select 'Error Pages' and create custom responses for specific HTTP status codes (e.g., 403, 404, 500). You can redirect to a custom error page on S3 or a different origin. Set the TTL for the error response (default 300s) to avoid repeated origin fetch attempts while the error persists. For 403/404, consider serving a custom JSON response from a Lambda@Edge function.
CNAME records point to a domain name but cannot be used at the zone apex (root domain like example.com). Alias records are Route 53-specific and can point to AWS resources (CloudFront, ELB, S3 website endpoints) at the apex without extra cost. Alias records also support 'Evaluate target health', which CNAMEs do not. Always use alias records when your target is an AWS resource — they're free and more capable.
- Enable Brotli compression — reduces data transfer 20-25% for text content. 2. Increase cache hit ratio by optimising cache keys (forward only necessary query strings/headers). 3. Set long TTLs (1 year) for versioned static assets. 4. Use Origin Shield to reduce origin data transfer costs (origin egress often costs more than CloudFront). 5. Migrate to S3 for static assets (cheaper origin). 6. Use cost allocation tags and set budget alerts. 7. For high request rates, consider combining multiple small files into larger archives (reduces per-request charges).
20+ years shipping production infrastructure and CI/CD at scale. Everything here is grounded in real deployments.
That's AWS. Mark it forged?
9 min read · try the examples if you haven't