---
# === IDENTITY ===
id: software/system-design/cdn-design/2026
canonical_question: "How do I design a content delivery network (CDN)?"
aliases:
  - "CDN architecture design"
  - "content delivery network system design"
  - "design a CDN from scratch"
  - "CDN system design interview"
  - "edge caching architecture"
  - "CDN components and architecture"
  - "how does a CDN work internally"
  - "build a content delivery network"
  - "CDN caching strategy design"
  - "distributed content delivery system"
entity_type: software_reference
domain: software > system-design > cdn_design
region: global
jurisdiction: global
temporal_scope: 2020-2026

# === VERIFICATION ===
last_verified: 2026-02-23
confidence: 0.92
freshness: quarterly
version: 1.0
first_published: 2026-02-23

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: stable
  last_breaking_change: null
  next_review: 2026-08-22
  change_sensitivity: medium

# === CONSTRAINTS ===
constraints:
  - "Cache invalidation must be explicit and bounded — never rely solely on TTL expiry for time-sensitive content; always implement a purge mechanism"
  - "Origin servers must handle thundering herd (cache stampede) when popular content expires simultaneously across edge nodes — use request coalescing or stale-while-revalidate"
  - "TLS termination at the edge requires distributing private keys or using keyless SSL — never send private keys to third-party CDN edge nodes without encryption at rest"
  - "Anycast routing assumes symmetric paths — asymmetric internet routing can cause requests to land on suboptimal PoPs; always measure real-user latency, not just geographic distance"
  - "Cache key design must account for Vary headers (Accept-Encoding, Accept-Language) — ignoring Vary serves wrong content to users with different capabilities"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "Looking for how to configure a specific CDN product (CloudFront, Cloudflare, Akamai) rather than design principles"
    use_instead: "Consult the vendor's official documentation for product-specific configuration"
  - condition: "Need to debug CDN cache miss issues on an existing deployment"
    use_instead: "Check cache-control headers, cache key configuration, and origin response codes"
  - condition: "Designing a peer-to-peer content distribution system (BitTorrent-style)"
    use_instead: "P2P distribution is a fundamentally different architecture from CDN edge caching"

# === AGENT HINTS ===
inputs_needed:
  - key: scale
    question: "What scale of traffic are you designing for?"
    type: choice
    options: ["< 1K req/s", "1K-100K req/s", "> 100K req/s"]
  - key: content_type
    question: "What type of content will the CDN primarily serve?"
    type: choice
    options: ["Static assets (images, CSS, JS)", "Video streaming", "Dynamic/personalized content", "Mixed workload"]
  - key: edge_compute
    question: "Do you need to run custom logic at the edge (A/B testing, auth, personalization)?"
    type: choice
    options: ["Yes", "No"]

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/system-design/cdn-design/2026"
suggested_citation: "Source: knowledgelib.io — AI Knowledge Library (verified 2026-02-23)"

# === RELATED UNITS ===
related_kos:
  related_to:
    - id: "software/debugging/browser-cors-errors/2026"
      label: "Browser CORS Errors"
    - id: "software/debugging/ssl-tls-certificate-errors/2026"
      label: "SSL/TLS Certificate Errors"
  solves:
    - id: "software/debugging/nodejs-econnrefused/2026"
      label: "Node.js ECONNREFUSED (origin connectivity)"

# === SOURCES (7 authoritative sources) ===
# Types: official_docs, technical_blog, rfc_spec, academic_paper, community_resource, industry_report
# Reliability: high, moderate_high, moderate, moderate_low, low, authoritative
sources:
  - id: src1
    title: "Content Delivery Network (CDN) Reference Architecture"
    author: Cloudflare
    url: https://developers.cloudflare.com/reference-architecture/architectures/cdn/
    type: official_docs
    published: 2024-06-15
    reliability: authoritative
  - id: src2
    title: "CDN guidance - Azure Architecture Center"
    author: Microsoft
    url: https://learn.microsoft.com/en-us/azure/architecture/best-practices/cdn
    type: official_docs
    published: 2025-06-13
    reliability: high
  - id: src3
    title: "Amazon CloudFront Developer Guide"
    author: AWS
    url: https://docs.aws.amazon.com/cloudfront/
    type: official_docs
    published: 2025-01-01
    reliability: authoritative
  - id: src4
    title: "Cache Invalidation Overview - Cloud CDN"
    author: Google Cloud
    url: https://docs.cloud.google.com/cdn/docs/cache-invalidation-overview
    type: official_docs
    published: 2025-03-01
    reliability: high
  - id: src5
    title: "Akamai CDN Architecture: From EdgeWorkers to Guardicore in 2025"
    author: BlazingCDN
    url: https://blog.blazingcdn.com/en-us/akamai-cdn-architecture-edgeworkers-guardicore-2025
    type: technical_blog
    published: 2025-04-10
    reliability: moderate_high
  - id: src6
    title: "Best Edge Computing Platforms 2025: Complete Comparison Guide"
    author: Waves and Algorithms
    url: https://wavesandalgorithms.com/reviews/edge-computing-comparisons-review
    type: industry_report
    published: 2025-08-01
    reliability: moderate_high
  - id: src7
    title: "CDN Cache Invalidation Strategies - TTL, Purging, Versioning Guide"
    author: System Design School
    url: https://systemdesignschool.io/fundamentals/cdn-cache-invalidation
    type: community_resource
    published: 2025-05-01
    reliability: moderate_high
---

# How to Design a Content Delivery Network (CDN)

## TL;DR

- **Bottom line**: A CDN is a geographically distributed network of reverse-proxy servers that cache content at edge locations close to users, reducing latency from hundreds of milliseconds to single-digit milliseconds for cached content.
- **Key tool/command**: `Cache-Control: public, max-age=31536000, immutable` for versioned static assets; `Surrogate-Key` headers for tag-based cache invalidation.
- **Watch out for**: Cache stampede (thundering herd) when popular content expires simultaneously across all edge nodes — use request coalescing or stale-while-revalidate.
- **Works with**: Any HTTP/HTTPS origin (cloud, on-prem, object storage); all major providers (Cloudflare, CloudFront, Akamai, Fastly, Google Cloud CDN).

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- Cache invalidation must be explicit and bounded — never rely solely on TTL expiry for time-sensitive content; always implement a purge mechanism
- Origin servers must handle thundering herd when popular content expires simultaneously — use request coalescing or stale-while-revalidate
- TLS termination at the edge requires distributing private keys or using keyless SSL — never send private keys to third-party edge nodes without encryption at rest
- Anycast routing assumes symmetric paths — measure real-user latency, not just geographic distance
- Cache key design must account for `Vary` headers (`Accept-Encoding`, `Accept-Language`) — ignoring Vary serves wrong content to users with different capabilities

## Quick Reference

| Component | Role | Technology Options | Scaling Strategy |
|---|---|---|---|
| DNS Resolution | Routes users to nearest PoP via geographic or latency-based routing | Anycast DNS, GeoDNS, AWS Route 53, Cloudflare DNS | Anycast distributes automatically; add more PoPs for coverage |
| Edge Server (PoP) | Caches content and serves requests from the network edge | Nginx, Varnish, HAProxy, Envoy, custom (Cloudflare, Fastly) | Horizontal scaling: add more servers per PoP; add more PoPs globally |
| Origin Shield / Mid-Tier Cache | Intermediate cache layer between edge and origin; reduces origin load | Cloudflare Tiered Cache, CloudFront Regional Edge Cache, Varnish | Single shield per region; multiple shields for multi-region origins |
| Origin Server | Serves authoritative content when cache misses occur | Any HTTP server, S3, GCS, Azure Blob Storage | Vertical + horizontal scaling; auto-scaling groups behind load balancer |
| Cache Key Generator | Determines cache entry identity from URL, headers, cookies, query params | Custom logic per CDN provider; Vary header processing | Configure per content type; normalize query strings to improve hit ratio |
| TLS Termination | Decrypts HTTPS at the edge to enable caching and inspection | Let's Encrypt, ACM, Keyless SSL (Cloudflare), custom certificates | Automated certificate provisioning; SNI for multi-tenant edge |
| Cache Invalidation Service | Purges stale content across all edge nodes | Surrogate-Key (Fastly), Cache-Tag (Cloudflare), API purge (CloudFront) | Event-driven purge via webhooks; tag-based for surgical invalidation |
| Load Balancer | Distributes traffic across origin servers and handles failover | AWS ALB/NLB, Cloudflare Load Balancing, HAProxy, Envoy | Health checks + weighted routing; active-passive for DR |
| Edge Compute Runtime | Executes custom logic at the edge (auth, A/B testing, personalization) | Cloudflare Workers, Lambda@Edge, Fastly Compute, Akamai EdgeWorkers | Stateless functions; scale with request volume; KV stores for state |
| Observability Stack | Monitors cache hit ratio, latency, error rates, origin health | Prometheus + Grafana, Datadog, Cloudflare Analytics, CloudWatch | Aggregate per-PoP metrics; alert on hit ratio drops or origin errors |
| Request Coalescing | Collapses duplicate cache-miss requests to origin into a single fetch | Varnish (grace mode), Nginx (proxy_cache_lock), Cloudflare (built-in) | Automatic per-PoP; tune lock timeout based on origin response time |
| Content Compression | Reduces transfer size for text-based assets at the edge | Brotli, gzip, zstd; compress on-the-fly or pre-compress at origin | Compress at edge for dynamic; pre-compress at origin for static |
| WAF / DDoS Protection | Filters malicious traffic before it reaches origin | Cloudflare WAF, AWS Shield, Akamai Kona, Fastly Signal Sciences | Anycast absorbs volumetric attacks; WAF rules for application-layer |
| Logging & Analytics Pipeline | Captures access logs for billing, debugging, and analytics | Real-time logs (Cloudflare Logpush, CloudFront real-time logs), ELK | Stream to object storage; sample at high volume (1-10%) |

## Decision Tree

```
START
├── Primarily serving static assets (images, CSS, JS, fonts)?
│   ├── YES → Pull-based CDN with long TTLs + versioned URLs (fingerprinted filenames)
│   │   ├── < 1K req/s → Single-provider CDN (Cloudflare free tier, CloudFront)
│   │   └── > 1K req/s → Add origin shield + tiered caching to reduce origin load
│   └── NO ↓
├── Serving video or large file downloads?
│   ├── YES → Specialized media CDN with range-request support + chunked caching
│   │   ├── Live streaming → Use HLS/DASH with short-segment caching (2-6s TTL)
│   │   └── VOD → Long TTLs + byte-range caching + pre-warming popular content
│   └── NO ↓
├── Need dynamic/personalized content at the edge?
│   ├── YES → Edge compute platform (Cloudflare Workers, Lambda@Edge, Fastly Compute)
│   │   ├── Personalization varies by < 10 dimensions → Cache variants with custom cache keys
│   │   └── Highly dynamic → Edge-side includes (ESI) or edge compute assembly
│   └── NO ↓
├── Need to run in multiple cloud providers or avoid vendor lock-in?
│   ├── YES → Multi-CDN strategy with DNS-based traffic steering (Cedexis/Citrix ITM, NS1)
│   └── NO ↓
└── DEFAULT → Pull-based CDN with origin shield, tag-based invalidation, and stale-while-revalidate
```

## Step-by-Step Guide

### 1. Define your content taxonomy and caching strategy

Classify all content into caching tiers based on mutability and personalization. This determines TTLs, cache keys, and invalidation strategies for each content type. [src1]

```yaml
# Content caching taxonomy
content_tiers:
  immutable_assets:
    pattern: "*.{js,css,woff2,png,jpg,webp}"
    cache_control: "public, max-age=31536000, immutable"
    cache_key: "URL (filename includes content hash)"
    invalidation: "Deploy new filename (never purge)"
    example: "/assets/app.a1b2c3d4.js"

  semi_static:
    pattern: "*.html, /api/catalog"
    cache_control: "public, max-age=300, stale-while-revalidate=86400"
    cache_key: "URL + Accept-Encoding"
    invalidation: "Tag-based purge on publish"
    example: "/products/widget.html"

  personalized:
    pattern: "/dashboard/*, /api/user/*"
    cache_control: "private, no-store"
    cache_key: "Not cached at CDN edge"
    invalidation: "N/A"
    example: "/dashboard/settings"

  api_responses:
    pattern: "/api/v1/*"
    cache_control: "public, max-age=60, stale-if-error=300"
    cache_key: "URL + query params + Accept header"
    invalidation: "Short TTL + event-driven purge"
    example: "/api/v1/products?category=electronics"
```

**Verify**: Review origin responses with `curl -sI https://origin.example.com/assets/app.js | grep -i cache-control` -- expected: `Cache-Control: public, max-age=31536000, immutable`

### 2. Design the edge network topology

Choose between single-tier (edge-only) and multi-tier (edge + origin shield) based on your origin's capacity and geographic distribution. For most production systems, a two-tier architecture (edge PoPs + regional origin shields) is recommended. [src1] [src3]

```
                    ┌──────────────┐
                    │   Client     │
                    └──────┬───────┘
                           │ DNS (Anycast)
                    ┌──────▼───────┐
                    │  Edge PoP    │◄── L1 Cache (hot content, small)
                    │  (nearest)   │    TTL: seconds to hours
                    └──────┬───────┘
                           │ Cache MISS
                    ┌──────▼───────┐
                    │ Origin Shield│◄── L2 Cache (warm content, large)
                    │  (regional)  │    Collapses duplicate misses
                    └──────┬───────┘
                           │ Cache MISS (collapsed)
                    ┌──────▼───────┐
                    │ Origin Server│◄── Authoritative source
                    │  (your infra)│    Handles only unique misses
                    └──────────────┘
```

**Verify**: After configuring tiered caching, check response headers for `CF-Cache-Status: HIT` (edge) or `X-Cache: Hit from cloudfront` (CloudFront) to confirm multi-tier caching is active.

### 3. Implement cache key normalization

Poor cache key design is the number one cause of low hit ratios. Normalize query parameters, strip tracking parameters, and handle `Vary` headers correctly. [src2]

```nginx
# Nginx: Normalize cache key to improve hit ratio
# Strip marketing query params (utm_*, fbclid, gclid)
# Sort remaining query params for consistent cache keys

map $args $normalized_args {
    default $args;
    "~*^(.*)(?:&|^)(utm_[^&]*|fbclid[^&]*|gclid[^&]*)(.*)$" "$1$3";
}

proxy_cache_key "$scheme$request_method$host$uri$normalized_args";

# Handle Vary correctly — only vary on what matters
proxy_hide_header Vary;
add_header Vary "Accept-Encoding" always;
```

**Verify**: Test with `curl -sI 'https://cdn.example.com/page?b=2&a=1' | grep X-Cache-Key` and `curl -sI 'https://cdn.example.com/page?a=1&b=2' | grep X-Cache-Key` -- both should produce the same cache key.

### 4. Set up cache invalidation with surrogate keys

Tag-based invalidation lets you surgically purge related content without full cache flushes. Attach surrogate keys (cache tags) to every response from your origin. [src4] [src7]

```python
# Python (FastAPI): Attach surrogate keys to responses for tag-based purging
from fastapi import FastAPI, Response

app = FastAPI()

@app.get("/products/{product_id}")
async def get_product(product_id: str, response: Response):
    product = await fetch_product(product_id)

    # Attach multiple surrogate keys for granular invalidation
    # Purging "product-123" invalidates just this product
    # Purging "category-electronics" invalidates all electronics products
    response.headers["Surrogate-Key"] = (
        f"product-{product_id} "
        f"category-{product.category} "
        f"all-products"
    )
    response.headers["Cache-Control"] = "public, max-age=3600, stale-while-revalidate=86400"

    return product
```

**Verify**: `curl -sI https://cdn.example.com/products/123 | grep -i surrogate-key` -- expected: `Surrogate-Key: product-123 category-electronics all-products`

### 5. Implement request coalescing to prevent cache stampede

When a popular cache entry expires, hundreds of simultaneous requests can overwhelm the origin (thundering herd). Configure request coalescing so only one request goes to origin while others wait. [src1]

```nginx
# Nginx: Request coalescing with proxy_cache_lock
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=cdn_cache:100m
                 max_size=10g inactive=24h use_temp_path=off;

server {
    location / {
        proxy_cache cdn_cache;
        proxy_cache_lock on;              # Only 1 request to origin per cache key
        proxy_cache_lock_timeout 5s;      # Wait up to 5s for the first request
        proxy_cache_lock_age 5s;          # After 5s, let another request through
        proxy_cache_use_stale error timeout updating http_500 http_502 http_503;
        proxy_cache_background_update on; # Refresh cache in background

        proxy_pass http://origin_upstream;
    }
}
```

**Verify**: Simulate concurrent requests with `ab -n 100 -c 50 https://cdn.example.com/popular-page` and check origin access logs -- should see only 1 request during the lock window, not 50.

### 6. Configure edge compute for dynamic logic

Edge compute lets you run authentication, A/B testing, geolocation routing, and header manipulation at the edge without round-tripping to origin. [src5] [src6]

```javascript
// Cloudflare Workers: Edge-side A/B testing with consistent assignment
// Input:  Incoming HTTP request
// Output: Proxied request to variant A or B origin, with sticky assignment

export default {
  async fetch(request, env) {
    const url = new URL(request.url);

    // Check for existing assignment cookie
    const cookies = request.headers.get("Cookie") || "";
    const match = cookies.match(/ab_variant=([AB])/);
    let variant = match ? match[1] : null;

    // Assign new users 50/50
    if (!variant) {
      variant = Math.random() < 0.5 ? "A" : "B";
    }

    // Route to variant-specific origin
    const origins = {
      A: "https://origin-a.example.com",
      B: "https://origin-b.example.com",
    };

    const originUrl = origins[variant] + url.pathname + url.search;
    const response = await fetch(originUrl, {
      headers: request.headers,
    });

    // Clone response and set sticky cookie
    const newResponse = new Response(response.body, response);
    if (!match) {
      newResponse.headers.append(
        "Set-Cookie",
        `ab_variant=${variant}; Path=/; Max-Age=86400; Secure; HttpOnly`
      );
    }

    return newResponse;
  },
};
```

**Verify**: `curl -v https://cdn.example.com/test-page 2>&1 | grep -i 'set-cookie.*ab_variant'` -- expected: `Set-Cookie: ab_variant=A; Path=/; Max-Age=86400; Secure; HttpOnly` (or variant B)

## Code Examples

### Python: Programmatic cache purge via Cloudflare API

> Full script: [python-programmatic-cache-purge-via-cloudflare-api.py](scripts/python-programmatic-cache-purge-via-cloudflare-api.py) (26 lines)

```python
# Input:  List of URLs or cache tags to purge
# Output: Purge confirmation from Cloudflare API
import httpx  # httpx==0.27.0
CLOUDFLARE_API = "https://api.cloudflare.com/client/v4"
ZONE_ID = "your-zone-id"
# ... (see full script)
```

### JavaScript/TypeScript: CloudFront invalidation via AWS SDK

> Full script: [javascript-typescript-cloudfront-invalidation-via-.ts](scripts/javascript-typescript-cloudfront-invalidation-via-.ts) (28 lines)

```typescript
// Input:  Array of path patterns to invalidate
// Output: Invalidation ID for tracking
import {
  CloudFrontClient,
  CreateInvalidationCommand,
# ... (see full script)
```

### Go: Custom origin shield with request coalescing

> Full script: [go-custom-origin-shield-with-request-coalescing.go](scripts/go-custom-origin-shield-with-request-coalescing.go) (46 lines)

```go
// Input:  Concurrent requests for the same cache key
// Output: Single origin fetch, result shared across all waiters
package main
import (
    "context"
# ... (see full script)
```

## Anti-Patterns

### Wrong: Purging entire cache on every deploy

```bash
# BAD — nuclear purge destroys all cached content globally
# Every user gets a cache miss after deploy, overwhelming the origin
curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE/purge_cache" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"purge_everything": true}'
```

### Correct: Use versioned filenames for assets, tag-based purge for HTML

```bash
# GOOD — versioned assets never need purging (new hash = new URL)
# Only purge HTML/API responses that reference the new asset URLs
curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE/purge_cache" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"tags": ["page-html", "api-catalog"]}'
# Assets like /app.a1b2c3.js are immutable — old versions expire via TTL
```

### Wrong: Setting the same TTL for all content types

```nginx
# BAD — one TTL for everything means either stale dynamic content
# or unnecessarily short caching for static assets
location / {
    add_header Cache-Control "public, max-age=3600";
    proxy_pass http://origin;
}
```

### Correct: Tiered TTLs based on content mutability

```nginx
# GOOD — different TTLs for different content types
location ~* \.(js|css|woff2|png|jpg|webp)$ {
    add_header Cache-Control "public, max-age=31536000, immutable";
}
location ~* \.html$ {
    add_header Cache-Control "public, max-age=300, stale-while-revalidate=86400";
}
location /api/ {
    add_header Cache-Control "public, max-age=60, stale-if-error=300";
}
```

### Wrong: Caching responses with Set-Cookie headers

```python
# BAD — caching a response that contains Set-Cookie means every user
# gets the same session cookie, causing session hijacking
@app.get("/login-page")
async def login_page(response: Response):
    response.headers["Cache-Control"] = "public, max-age=3600"
    response.set_cookie("session_id", generate_session())  # Cached!
    return {"page": "login"}
```

### Correct: Strip Set-Cookie or mark as private

```python
# GOOD — responses with user-specific headers must not be cached publicly
@app.get("/login-page")
async def login_page(response: Response):
    response.headers["Cache-Control"] = "private, no-store"
    response.set_cookie("session_id", generate_session())
    return {"page": "login"}

@app.get("/public-page")
async def public_page(response: Response):
    # No Set-Cookie on cacheable responses
    response.headers["Cache-Control"] = "public, max-age=3600"
    return {"page": "public_content"}
```

### Wrong: Using query strings for cache busting without normalization

```html
<!-- BAD — marketing tools append random params, creating infinite cache variants -->
<!-- /style.css?v=1&utm_source=twitter&fbclid=abc is a different cache entry than /style.css?v=1 -->
<link rel="stylesheet" href="/style.css?v=1">
```

### Correct: Content-hash filenames + strip tracking params at the edge

```html
<!-- GOOD — content hash in filename; no query string needed -->
<link rel="stylesheet" href="/style.d4e5f6.css">
```

```nginx
# Edge config: strip tracking params so they don't fragment the cache
if ($args ~* "(^|&)(utm_[^&]*|fbclid[^&]*|gclid[^&]*)") {
    rewrite ^(.*)$ $1? permanent;
}
```

## Common Pitfalls

- **Low cache hit ratio due to excessive Vary headers**: If the origin sends `Vary: *` or `Vary: Cookie`, the CDN creates a separate cache entry per unique cookie value, effectively disabling caching. Fix: `Strip unnecessary Vary headers at the edge; only vary on Accept-Encoding for compression`. [src1]
- **Cache stampede after TTL expiry**: When a popular resource expires across all PoPs simultaneously, hundreds of requests hit the origin at once. Fix: `Use stale-while-revalidate to serve stale content while refreshing in the background; enable request coalescing (proxy_cache_lock in Nginx)`. [src2]
- **Mixed content after CDN adoption**: Origin still serves HTTP links while CDN terminates TLS, causing browser mixed-content warnings. Fix: `Use protocol-relative URLs or force HTTPS at the origin; set Content-Security-Policy: upgrade-insecure-requests`. [src2]
- **Cache poisoning via unkeyed headers**: If the cache key ignores a header that affects the response (e.g., `X-Forwarded-Host`), an attacker can poison the cache with a malicious response. Fix: `Include all response-affecting headers in the cache key, or normalize and validate them at the edge`. [src1]
- **Stale content served after origin update**: Origin content changed but CDN still serves the old cached version because TTL has not expired. Fix: `Implement active cache invalidation (purge API or surrogate keys) triggered by your CI/CD pipeline; do not rely solely on TTL for mutable content`. [src4] [src7]
- **CORS failures on CDN-served assets**: Browser blocks font or API responses because the CDN strips or does not forward CORS headers from origin. Fix: `Configure the CDN to forward Access-Control-Allow-Origin headers from origin, or add them at the edge layer`. [src2]
- **CloudFront invalidation cost surprise**: CloudFront charges $0.005 per path after the first 1,000 free invalidations/month. Wildcard invalidations (`/products/*`) count as one path but invalidate broadly. Fix: `Use versioned filenames for assets to avoid invalidation entirely; batch HTML invalidations and use wildcards strategically`. [src3]
- **Edge compute cold starts**: Lambda@Edge functions can add 100-500ms of latlight on cold starts. Cloudflare Workers and Fastly Compute use V8 isolates with sub-millisecond cold starts. Fix: `Choose isolate-based edge compute (Workers, Fastly Compute) for latency-sensitive paths; use Lambda@Edge only for viewer-request triggers where cold starts are amortized`. [src6]

## Diagnostic Commands

```bash
# Check cache status of a specific URL
curl -sI https://cdn.example.com/page | grep -iE 'cache-control|x-cache|cf-cache-status|age|vary'

# Measure cache hit ratio over time (Cloudflare)
curl -s "https://api.cloudflare.com/client/v4/zones/$ZONE/analytics/dashboard?since=-1440" \
  -H "Authorization: Bearer $TOKEN" | jq '.result.totals.requests.cached / .result.totals.requests.all'

# Test origin response without CDN (bypass cache)
curl -sI https://cdn.example.com/page -H "Cache-Control: no-cache" -H "Pragma: no-cache"

# Verify surrogate keys are present in origin response
curl -sI https://origin.example.com/products/123 | grep -i surrogate-key

# Check if compression is active
curl -sI https://cdn.example.com/style.css -H "Accept-Encoding: br,gzip" | grep -i content-encoding

# Trace CDN routing (which PoP served the request)
curl -sI https://cdn.example.com/ | grep -iE 'cf-ray|x-served-by|x-amz-cf-pop|server-timing'
```

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Serving static assets (images, CSS, JS) to a global audience | All users are in the same datacenter as the origin | Direct origin serving with local reverse proxy (Nginx/Varnish) |
| Reducing origin load during traffic spikes or viral events | Content is 100% personalized with no shared components | Application-level caching (Redis) + server-side rendering |
| Improving Time to First Byte (TTFB) for geographically distributed users | Data sovereignty requires content to stay in a specific country | Regional deployment with geo-fencing, not global CDN |
| Protecting origin from DDoS attacks via anycast absorption | Serving real-time websocket connections or long-polling | Direct connection to origin; CDN websocket support is limited |
| Offloading TLS termination and certificate management | Content changes on every request with no cacheability | API gateway with rate limiting (Kong, Apigee) instead |
| Running lightweight edge logic (auth, redirects, A/B tests) | Need complex server-side rendering with database access at the edge | Edge-native databases (Cloudflare D1, Turso) + full-stack edge framework |

## Important Caveats

- CDN cache hit ratios below 80% indicate a configuration problem, not a CDN limitation — the most common causes are excessive Vary headers, unstripped query parameters, and Set-Cookie on cacheable responses
- Multi-CDN setups require careful DNS TTL management — if DNS TTL is longer than your failover detection time, users will continue hitting the failed CDN for the duration of the DNS TTL
- Edge compute pricing models vary dramatically — Cloudflare Workers charges per request (included in plans), Lambda@Edge charges per request + duration + memory, Fastly Compute charges per request + compute time; model your expected traffic before committing
- Cache invalidation propagation is not instant — Cloudflare purges propagate in ~30 seconds globally, CloudFront invalidations take 5-15 minutes, and some CDNs only guarantee "best effort" timing
- Origin shield adds latency to cache misses (extra hop) but dramatically reduces origin load — only enable it when origin protection matters more than miss latency (true for most production systems)

## Related Units

- [Browser CORS Errors](/software/debugging/browser-cors-errors/2026)
- [SSL/TLS Certificate Errors](/software/debugging/ssl-tls-certificate-errors/2026)
- [Node.js ECONNREFUSED](/software/debugging/nodejs-econnrefused/2026)
