CDNs reduce latency by caching content at edge servers near users
Origin server stores authoritative copy; edge servers serve cached copies
Anycast DNS routes users to the nearest Point of Presence (PoP)
Cache hit ratio above 80% is the target; below means misconfiguration
Most CDN failures come from incorrect Cache-Control headers or query string fragmentation
Push CDN pre-positions large files; Pull CDN is the default for websites
Plain-English First
Imagine your favourite pizza place has only one kitchen in New York. If you order from Los Angeles, they'd have to ship a pizza across the country — cold, late, and sad. A CDN is like opening mini pizza kitchens in every major city so your pizza arrives hot and fast no matter where you live. The original kitchen (your origin server) still makes the recipe, but the local kitchens (edge servers) serve most customers. That's a CDN in one bite.
Every time a user in Tokyo loads a website hosted in Dublin, their browser is waiting for data to travel thousands of miles under oceans and through dozens of routers. At the speed of light, that still takes 150–200ms — and that's before the server even starts processing. For a video stream, an e-commerce checkout, or a multiplayer game, that latency is the difference between a great experience and an abandoned session. Netflix, Cloudflare, and Amazon didn't become giants by ignoring this problem — they solved it with CDNs.
A Content Delivery Network solves the physical distance problem by pre-positioning your content closer to your users. Instead of every request travelling back to one origin server, a global mesh of edge servers handles the heavy lifting locally. This cuts latency, reduces bandwidth costs, absorbs traffic spikes, and adds a layer of protection against DDoS attacks — all at the same time.
By the end of this article you'll be able to draw the full CDN request lifecycle on a whiteboard, explain what each component does and why it exists, understand the trade-offs between push and pull CDNs, and answer the tricky CDN questions that come up in system design interviews. Let's build the mental model from the ground up.
The Origin Server — The Single Source of Truth
Every CDN story starts at the origin server. This is your actual application server — the one running your Node.js API, your Django app, or your S3 bucket. It holds the canonical, authoritative copy of every asset: HTML pages, images, JavaScript bundles, videos, API responses.
The origin server's job in a CDN architecture is NOT to serve every user request. That would defeat the whole point. Its job is to serve content to edge servers when they don't have a cached copy, and to handle requests that genuinely can't be cached — like a personalised checkout page or a real-time bank balance.
This distinction matters enormously. A poorly designed CDN setup routes too much traffic back to the origin, creating a bottleneck. A well-designed one keeps the origin cache-hit ratio (the percentage of requests served without hitting origin) as close to 100% as possible for static assets, while intelligently routing dynamic requests.
Think of the origin as a master chef who trains local chefs. The master chef doesn't cook every meal — they set the recipe, train the team, and only get called in for something truly bespoke.
origin_server_cache_headers.confNGINX
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
# Origin server Nginx configuration
# This tells CDN edge servers HOW to cache each type of content
server {
listen 80;
server_name origin.theforge.io;
# Static assets — cache aggressively at the edge for1 year
# TheCDN will serve these without touching origin on repeat requests
location ~* \.(js|css|png|jpg|woff2|svg)$ {
root /var/www/static;
# 'public' means CDN edge servers ARE allowed to cache this
# 'max-age=31536000' = 1 year in seconds
# 'immutable' tells the browser: don't even check for updates
add_header Cache-Control"public, max-age=31536000, immutable";
# ETag lets CDN validate freshness without re-downloading
etag on;
}
# HTML pages — short cache, must check origin for updates
location ~* \.html$ {
root /var/www/html;
# 'stale-while-revalidate' serves cached copy instantly
# while refreshing it in the background — best of both worlds
add_header Cache-Control"public, max-age=300, stale-while-revalidate=60";
}
# API responses — personalised data must NOT be cached at CDN level
# 'private' means only the user's browser can cache it, not edge servers
location /api/user/ {
proxy_pass http://app_backend;
add_header Cache-Control"private, no-store";
}
# PublicAPIdata (e.g. product catalogue) — safe to cache at edge
location /api/products/ {
proxy_pass http://app_backend;
add_header Cache-Control"public, max-age=600"; # 10 minutes
}
}
Output
# When a CDN edge server requests /static/app.js from origin:
# Edge server stores it locally. Next 10,000 users in that region
# get the file FROM THE EDGE — origin never sees those requests.
Watch Out: Cache-Control vs Expires
Never rely on the old 'Expires' header for CDN caching. 'Cache-Control' always wins when both are present, and modern CDNs often ignore 'Expires' entirely. Stick to 'Cache-Control: public, max-age=N' for CDN-cacheable content and 'Cache-Control: private, no-store' for anything user-specific.
Production Insight
Missing Cache-Control headers cause 100% of requests to hit origin, defeating the CDN.
A single missing header during a traffic spike can DDoS your own origin server.
Rule: always set explicit Cache-Control headers on every response, even for dynamic content.
Key Takeaway
The origin server's job is not to serve every user.
It exists to serve edge servers that haven't cached yet.
Design Cache-Control headers to keep origin traffic minimal for static assets.
Choosing Cache-Control Directives
IfContent is identical for all users (static assets, public APIs)
→
UseUse 'public, max-age=N' where N is appropriate TTL (1 year for versioned files).
IfContent is user-specific (profile pages, account settings)
→
UseUse 'private, no-store' to prevent CDN caching altogether.
IfContent changes frequently but is the same for all users (HTML, product listings)
→
UseUse 'public, max-age=300, stale-while-revalidate=60' for freshness with performance.
Edge Servers and PoPs — Your CDN's Global Muscle
Edge servers are the beating heart of a CDN. They sit inside Points of Presence (PoPs) — physical data centres strategically placed in cities around the world. Cloudflare has 300+ PoPs. Akamai has 4,000+. The denser the network, the shorter the physical path to any user on earth.
When a user requests content, DNS routing (usually Anycast DNS) directs them to the nearest PoP automatically — not nearest by map distance, but nearest by network latency, which accounts for real-world internet topology. A user in Singapore might actually be routed to a PoP in Hong Kong if that path has lower latency.
Each edge server maintains its own local cache. When a request arrives, the edge server checks its cache first. A cache HIT means the content is served immediately — no origin involved, no ocean crossed. A cache MISS means the edge server fetches the content from origin, stores it locally, and serves it. Every subsequent request for that content is a HIT.
Edge servers also do more than cache. Modern CDN edges run TLS termination (handling HTTPS handshakes locally so the encryption overhead doesn't add to origin round-trips), HTTP/2 and HTTP/3 multiplexing, image optimisation, and increasingly, full serverless compute via edge functions.
cloudflare_edge_worker.jsJAVASCRIPT
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
46
47
48
49
50
51
52
53
54
55
56
57
58
// Cloudflare Worker — code that runs ON the edge server, not your origin// This runs in 300+ cities simultaneously with ~0ms cold startadaddEventListener('fetch', event => {
event.respondWith(handleRequest(event.request));
});
asyncfunctionhandleRequest(incomingRequest) {
const requestUrl = newURL(incomingRequest.url);
// --- PERSONALISED CACHING STRATEGY ---// We can't cache the full page for logged-in users,// but we CAN cache the static shell and inject user data at the edgeconst isStaticAsset = requestUrl.pathname.startsWith('/static/');
const isApiRequest = requestUrl.pathname.startsWith('/api/');
if (isStaticAsset) {
// Let the CDN cache handle this — fetch from cache or originconst cachedResponse = await caches.default.match(incomingRequest);
if (cachedResponse) {
// CACHE HIT: add a header so we can monitor hit rates in logsconst hitResponse = newResponse(cachedResponse.body, cachedResponse);
hitResponse.headers.set('X-Cache-Status', 'HIT');
return hitResponse;
}
// CACHE MISS: fetch from origin and store at this edge locationconst originResponse = awaitfetch(incomingRequest);
const responseToCache = originResponse.clone(); // clone before consuming body
event.waitUntil(
caches.default.put(incomingRequest, responseToCache) // store asynchronously
);
const missResponse = newResponse(originResponse.body, originResponse);
missResponse.headers.set('X-Cache-Status', 'MISS');
return missResponse;
}
if (isApiRequest) {
// For API requests, add geographic context so origin// can serve localised data without client needing to send itconst modifiedRequest = newRequest(incomingRequest, {
headers: {
...Object.fromEntries(incomingRequest.headers),
'X-User-Country': incomingRequest.cf.country, // e.g. 'JP'
'X-User-City': incomingRequest.cf.city, // e.g. 'Tokyo'
'X-User-Timezone': incomingRequest.cf.timezone // e.g. 'Asia/Tokyo'
}
});
return fetch(modifiedRequest); // pass through to origin with enriched headers
}
// Default: pass through everything else unchangedreturnfetch(incomingRequest);
}
Output
# First request from Tokyo user (Cache MISS):
# GET /static/hero-image.webp
# X-Cache-Status: MISS
# Response time: 180ms (edge → origin in Dublin → back to Tokyo)
#
# Second request from different Tokyo user (Cache HIT):
# GET /static/hero-image.webp
# X-Cache-Status: HIT
# Response time: 8ms (served from Tokyo PoP local storage)
#
# API request enriched at edge:
# X-User-Country: JP
# X-User-City: Tokyo
# X-User-Timezone: Asia/Tokyo
# (Origin can now serve localised pricing/content automatically)
Pro Tip: Monitor Your Cache Hit Ratio
A CDN cache hit ratio below 80% means you're paying for CDN infrastructure but not getting the speed benefits. Check for overly aggressive cache-busting, missing Cache-Control headers on static assets, or URL query strings that are creating unnecessary cache key variations (e.g. ?session_id=xyz making every URL unique).
Production Insight
Cache misses in a new region cause a 'thundering herd' against origin when many users arrive simultaneously.
If origin can't handle the burst, it cascades into a full outage.
Rule: pre-warm edge servers for new deployments or use staggered rollouts.
Key Takeaway
Edge servers are where speed happens.
Cache hit ratio is the north-star metric.
Below 80% means your CDN is expensive middleware, not a performance multiplier.
When to Pre-Warm an Edge PoP
IfLaunching a product in a new geographic region
→
UseUse CDN's pre-warm feature to push popular assets to that PoP before go-live.
IfRunning a flash sale with expected high traffic
→
UsePre-warm product images and pricing CSS at all PoPs 30 minutes before start.
IfNormal daily traffic with existing users
→
UseNo pre-warming needed; natural cache misses will fill the edge over time.
Pull CDN vs Push CDN — Choosing the Right Delivery Model
There are two fundamentally different ways a CDN can get content from your origin to its edge servers. Understanding which model fits your use case is one of the most important CDN architecture decisions you'll make.
A Pull CDN is lazy — in a good way. Edge servers only fetch content from your origin when a user requests it and it's not already cached. The first request to each PoP causes a cache miss and hits origin. Every subsequent request is a cache hit. This is the default model for most CDNs (Cloudflare, Fastly) and works brilliantly for websites with lots of assets and unpredictable traffic patterns.
A Push CDN is proactive. You explicitly upload content to the CDN's edge servers before anyone requests it. You control exactly what's cached and where. This is ideal for large files you know will be popular — like a new movie upload on a streaming service, or a software release binary. You push once, the file is globally pre-positioned, and the first user in any region gets a cache hit.
The trade-off is control vs automation. Push gives you precision but requires you to manage invalidation. Pull is hands-off but means early users in each region always experience that first-hit latency penalty.
cdn_push_deploy_pipeline.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
46
47
48
49
50
51
52
53
54
55
#!/bin/bash
# PushCDN deployment script — used in a CI/CD pipeline after a new build
# This pre-warms edge servers so users NEVER see a cache miss on launch day
set -e # Exit immediately if any command fails
CDN_PROVIDER_API="https://api.bunny.net"
CDN_API_KEY="${BUNNY_API_KEY}" # Loaded from CI/CD secrets, never hardcoded
STORAGE_ZONE="theforge-assets" # YourCDN storage zone name
BUILD_DIR="./dist" # Local build output directory
echo "=== Starting Push CDN Deployment ==="
echo "Build directory: $BUILD_DIR"
echo "Target storage zone: $STORAGE_ZONE"
# Step1: Upload all newstatic assets to the CDN storage zone
# TheCDN then distributes these to ALL edge PoPs proactively
for asset_file in $(find "$BUILD_DIR" -type f); do
# Derive the remote path by stripping the local build prefix
remote_path="${asset_file#$BUILD_DIR/}"
echo "Uploading: $remote_path"
# Determine content type based on file extension for correct headers
content_type="application/octet-stream" # safe defaultcase"$asset_file" in
*.js) content_type="application/javascript" ;;
*.css) content_type="text/css" ;;
*.html) content_type="text/html" ;;
*.webp) content_type="image/webp" ;;
*.woff2) content_type="font/woff2" ;;
esac
# PUT the file to CDN storage — CDN propagates it to all PoPs
curl --silent --show-error \
--request PUT \
--header "AccessKey: $CDN_API_KEY" \
--header "Content-Type: $content_type" \
--data-binary "@$asset_file" \
"$CDN_PROVIDER_API/storage/v2/$STORAGE_ZONE/$remote_path"
done
# Step2: Purge old cached versions so users get the new files immediately
# Withoutthis step, edge servers serve stale content until TTL expires
echo "=== Purging stale CDN cache ==="
curl --silent --show-error \
--request POST \
--header "AccessKey: $CDN_API_KEY" \
--header "Content-Type: application/json" \
--data '{"async": true}' \
"$CDN_PROVIDER_API/pullzone/theforge-zone/purgeCache"
echo "=== Push CDN Deployment Complete ==="
echo "Assets pre-positioned at all edge PoPs. First users globally get cache HITs."
Output
=== Starting Push CDN Deployment ===
Build directory: ./dist
Target storage zone: theforge-assets
Uploading: js/app.a1b2c3d4.js
Uploading: css/styles.e5f6g7h8.css
Uploading: images/hero.webp
Uploading: fonts/inter.woff2
=== Purging stale CDN cache ===
=== Push CDN Deployment Complete ===
Assets pre-positioned at all edge PoPs. First users globally get cache HITs.
Interview Gold: When Would You Choose Push Over Pull?
Use Push CDN when: (1) you have large files (>10MB) that would be slow to pull from origin on first request, (2) you have a known traffic event (product launch, live stream) and can't risk cache-miss latency for the first wave of users, or (3) your origin can't handle the spike of simultaneous cache-miss requests. Pull CDN covers everything else — it's simpler and self-managing.
Production Insight
Push CDN requires explicit invalidation when content changes; forget it and users see stale files for TTL duration.
Pull CDN can cause origin meltdown when a new asset goes viral across multiple PoPs simultaneously.
Rule: use Push for known events, Pull for organic traffic, and always monitor cache hit ratio.
Key Takeaway
Pull CDN is the right default for most web applications.
Only switch to Push CDN when you have large files, predictable high-demand events, or an origin that can't absorb cache-miss spikes.
Monitor your cache hit ratio to validate your choice.
Which CDN Model to Use
IfStandard website with varied traffic, lots of small files
→
UsePull CDN — lower operational overhead, good for long-tail assets.
IfLarge downloadable files or streaming media with predictable demand
→
UsePush CDN — pre-position large files, avoid cold-start latency for first users.
IfHybrid: static site with occasional large releases
→
UseUse Pull CDN as default, and preload new release assets via push or CDN API pre-warm.
Cache Invalidation — The Hardest Problem in CDN Architecture
Phil Karlton famously said there are only two hard things in computer science: cache invalidation and naming things. CDN cache invalidation is where that joke stops being funny and starts costing companies money.
The problem is this: you've set a 24-hour TTL on your product images. You discover one image has the wrong price printed on it. That image is now cached at 300+ edge locations worldwide. Every user will see the wrong price for up to 24 hours unless you explicitly invalidate the cache.
The two main invalidation strategies are TTL-based expiry (set a reasonable TTL and let it expire naturally — simple but not instant) and explicit purge (call the CDN API to immediately evict specific assets — instant but requires integration work).
The smartest strategy combines both: use cache-busting filenames (e.g. app.a1b2c3.js where the hash changes on every build) with a long TTL. Since the filename changes with every deployment, you never need to invalidate — the old URL simply expires naturally, and all new traffic hits the new URL. Your HTML file, which references the new filename, gets a short TTL so it updates quickly. This is how most mature frontend build pipelines work.
cdn_cache_invalidation_api.jsJAVASCRIPT
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
// CDN Cache Invalidation Service// Programmatic cache purging after a content update// Real-world scenario: a product image was updated on the CMSconst https = require('https');
// --- CONFIGURATION ---
const CDN_ZONE_ID = process.env.CDN_ZONE_ID; // Your CDN zone identifier
const CDN_API_TOKEN = process.env.CDN_API_TOKEN; // Never hardcode credentials
/**
* Purges specific URLsfromCDN cache.
* Callthisfrom a CMS webhook when content is updated.
*
* @param {string[]} urlsToInvalidate - FullCDNURLs that need immediate cache eviction
* @returns {Promise<object>} - Purge job status fromCDN provider
*/
asyncfunctionpurgeUrlsFromCdnCache(urlsToInvalidate) {
console.log(`Purging ${urlsToInvalidate.length} URL(s) fromCDN cache...`);
// Cloudflare Cache Purge API payload formatconst purgePayload = JSON.stringify({
files: urlsToInvalidate // Accepts up to 30 URLs per request
});
returnnewPromise((resolve, reject) => {
const options = {
hostname: 'api.cloudflare.com',
path: `/client/v4/zones/${CDN_ZONE_ID}/purge_cache`,
method: 'POST',
headers: {
'Authorization': `Bearer ${CDN_API_TOKEN}`,
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(purgePayload)
}
};
const apiRequest = https.request(options, (apiResponse) => {
let responseBody = '';
apiResponse.on('data', chunk => { responseBody += chunk; });
apiResponse.on('end', () => {
const parsed = JSON.parse(responseBody);
if (parsed.success) {
console.log('Cache purge successful:', parsed.result);
resolve(parsed.result);
} else {
// Cloudflare returns structured errors — log them clearly
console.error('Cache purge failed:', parsed.errors);
reject(newError(parsed.errors[0].message));
}
});
});
apiRequest.on('error', reject);
apiRequest.write(purgePayload);
apiRequest.end();
});
}
// --- EXAMPLE USAGE ---// Triggered by a CMS webhook when a product image is updatedasyncfunctionhandleProductImageUpdate(updatedProductId) {
const affectedUrls = [
`https://assets.theforge.io/products/${updatedProductId}/hero.webp`,
`https://assets.theforge.io/products/${updatedProductId}/thumbnail.webp`,// Also purge the product page HTML so it reflects the new image
`https://www.theforge.io/products/${updatedProductId}`
];
try {
awaitpurgeUrlsFromCdnCache(affectedUrls);
console.log(`Product ${updatedProductId} is now live at all edge nodes.`);
} catch (error) {
console.error('Failed to purge CDN cache. Users may see stale content:', error.message);
// In production: alert on-call team, fallback to shorter TTL, or retry with backoff
}
}
handleProductImageUpdate('forge-keyboard-pro-2024');
Product forge-keyboard-pro-2024 is now live at all edge nodes.
# After purge completes (typically 1-5 seconds globally):
# All 300+ Cloudflare PoPs evict the stale image from local cache
# Next request to each PoP causes a cache MISS → fetches fresh image from origin
# Subsequent requests are cache HITs with the correct image
Pro Tip: Content-Hashed Filenames Eliminate Most Invalidation Needs
Configure your build tool (Webpack, Vite, Parcel) to include a content hash in asset filenames: 'app.[contenthash].js'. Each build produces a unique filename, so you set max-age=31536000 and never purge. Old files expire naturally. Only your HTML (which references the new filenames) needs a short TTL. This is the industry-standard approach used by Google, Facebook, and every major CDN-backed frontend.
Production Insight
A single missed invalidation during a price update caused a 20% revenue drop over 24 hours as users saw old prices.
The fix was implementing automated cache purging via webhook from the CMS, but the company lost trust.
Rule: automate invalidation from the content update source, never rely on manual TTL expiry for critical changes.
Key Takeaway
Content-hashed filenames + long TTLs eliminate the cache invalidation problem for static assets entirely.
Every senior frontend engineer uses this pattern.
If you're not doing it, start today.
Choosing an Invalidation Strategy
IfContent changes rarely and can tolerate some staleness (e.g., blog images)
→
UseSet long TTL (1 year) and rely on natural expiry. No automation needed.
IfContent changes frequently and must be instant (e.g., product prices)
→
UseUse short TTL (5 minutes) or implement cache purging via API on content update.
IfContent filenames change with each deployment (hash-based)
→
UseSet infinite TTL. No invalidation needed. Only HTML needs short TTL.
CDN Security: DDoS Protection, WAF, and TLS Termination at the Edge
A CDN isn't just a performance tool — it's also your first line of defence. By terminating TLS connections at the edge, the CDN handles encryption handshakes before they ever reach your origin. This offloads CPU-intensive crypto work and protects your origin from direct exposure to the internet.
Most enterprise CDNs include a Web Application Firewall (WAF) that runs on edge servers. WAF rules inspect incoming requests for SQL injection, XSS, and other OWASP Top 10 attacks — before they reach your application. The rules can be customised per path: strict rule set for login pages, relaxed for static assets.
DDoS mitigation is another core CDN security feature. The distributed nature of edge PoPs means that an attack targeting one region can be absorbed by hundreds of servers globally. CDN providers use traffic scrubbing centres to filter out malicious traffic while passing legitimate requests to origin. Cloudflare's 'Always Online' feature can even serve a cached version of your site when origin is down.
But security at the edge has trade-offs. Offloading TLS termination means your origin receives unencrypted HTTP requests from the CDN (in many setups). You must ensure the link between CDN and origin is secure — either via HTTPS between edge and origin, or by restricting origin access to CDN IP ranges only. This is called 'origin pull' security and is one of the most commonly misconfigured areas.
origin_security_for_cdn.confNGINX
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
# Nginx configuration for origin server behind a CDN
# Restricts access to CDNIP ranges only
# EnsuresTLS between CDN and origin (optional but recommended)
server {
listen 443 ssl http2;
server_name origin.theforge.io;
# Only allow requests from the CDN's publicIP ranges
# (CloudflareIPs as of 2026, check official list regularly)
allow 173.245.48.0/20;
allow 103.21.244.0/22;
# ... add all CDNIP ranges
deny all;
ssl_certificate /etc/ssl/certs/origin.crt;
ssl_certificate_key /etc/ssl/private/origin.key;
location / {
proxy_pass http://app_backend:3000;
# Trust the CDN's forwarded headers
proxy_set_header Host $host;
proxy_set_header X-Real-IP $http_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
# PreventCDN from forwarding malicious headers
proxy_set_header X-XSS-Protection"1; mode=block";
add_header Strict-Transport-Security"max-age=31536000; includeSubDomains" always;
}
}
Output
# With this config:
# - Only CDN edge servers can reach origin
# - Direct internet requests to origin IP are rejected (403)
# - TLS is enforced between CDN and origin
# - Origin sees the real user IP via X-Forwarded-For
# - HSTS header is added to all responses
Mental Model: The Castle Analogy
The outer wall (CDN edge) absorbs arrows (DDoS) and inspects all visitors (WAF).
Only pre-approved messengers (CDN IP ranges) are allowed through the gate to the inner keep (origin).
The outer wall handles the handshake (TLS) so the inner keep doesn't waste energy on encryption.
If the outer wall falls, the inner keep is still protected by moats (origin security groups).
Production Insight
Without restricting origin access to CDN IPs, attackers can bypass the CDN and directly target origin IP.
A single leaked origin IP can lead to DDoS bypassing all CDN protection.
Rule: always firewall origin to accept traffic only from CDN IP ranges, and use TLS between CDN and origin.
Key Takeaway
A CDN is your first line of defence.
Restrict origin access to CDN IPs and enable HTTPS end-to-end.
Use WAF rules for application-layer filtering before it hits your code.
CDN Security Decisions
IfCDN provider supports origin pull HTTPS
→
UseEnable HTTPS between CDN and origin for end-to-end encryption.
IfOrigin must allow direct admin access
→
UseUse a separate admin subdomain (admin.example.com) not proxied through CDN.
IfYou need to block specific countries or user agents
→
UseConfigure WAF rules at CDN level, not in application code.
● Production incidentPOST-MORTEMseverity: high
The Missing Cache-Control Header That Took Down a Black Friday Sale
Symptom
During a Black Friday flash sale, the e-commerce site became unresponsive. Origin server CPU hit 100%, database connections maxed out. CDN logs showed zero cache hits for all product images even though they were static assets — every request hit origin.
Assumption
The team assumed the CDN would cache all static assets by default. They had configured CDN caching rules in the provider dashboard but had not set Cache-Control headers on the origin server. The CDN provider was honoring origin headers, which were absent, so it treated everything as uncacheable.
Root cause
The origin server's Nginx configuration had no Cache-Control headers for static assets. The CDN provider's policy defaulted to 'no cache' when origin returns no caching directives. Result: every user request for a product image caused a full round-trip to origin.
Fix
Added Cache-Control: public, max-age=86400 to the static asset location block in Nginx. Then purged the (empty) CDN cache to force fresh headers. Within 5 minutes, cache hit ratio jumped to 94% and origin load dropped to 5%.
Key lesson
Never assume the CDN caches anything without explicit Cache-Control headers from origin.
Test cache behaviour with curl -I to inspect response headers before relying on the CDN in production.
Add a monitoring dashboard for CDN cache hit ratio and alert when it drops below 80%.
Production debug guideQuick diagnostic steps for the most common CDN production issues5 entries
Symptom · 01
Users report slow page loads despite CDN being enabled
→
Fix
Check Cache-Control headers on static assets with curl -I. If missing or set to 'private', the CDN is bypassed. Fix by adding 'public, max-age=N' on origin.
Symptom · 02
Cache hit ratio is below 70%
→
Fix
Inspect cache key fragmentation: are unique query strings (utm_*, session_id) being included? Configure CDN to ignore or normalize query parameters for static paths.
Symptom · 03
Stale content is served after a deployment
→
Fix
Verify if cache-busting filenames (content hash) are used. If not, trigger a CDN purge for the changed URLs. Confirm purge completion via CDN provider's API or UI.
Symptom · 04
Some users see other users' personal data
→
Fix
Immediately disable CDN caching for any URL that returns user-specific content. Audit all API endpoints for missing 'Cache-Control: private, no-store'. Never trust 'Vary: Cookie' alone.
Symptom · 05
Origin server CPU spikes coincide with traffic bursts to a new geographic region
→
Fix
Check if the CDN has edge servers in that region. If not, the first wave of users will all cause cache misses. Consider pre-warming the CDN or adding a PoP in that region.
★ CDN Debugging Cheat SheetCommands and actions to diagnose CDN issues in under 60 seconds
Is the CDN actually serving my content?−
Immediate action
Run curl with verbose headers and check for CDN-specific headers (e.g., X-Cache, CF-Cache-Status, X-Served-By).
Commands
curl -I https://cdn.example.com/static/app.js
Look for X-Cache: HIT or CF-Cache-Status: HIT
Fix now
If MISS or no CDN header, verify DNS points to CDN, then check origin Cache-Control headers.
Why is the cache hit ratio so low?+
Immediate action
Check CDN analytics for top URLs causing cache misses.
Commands
Dig into CDN logs: grep for 'MISS' or query provider API for top uncached URLs
Check for query string variations: curl 'https://.../file.jpg?v=1' vs 'https://.../file.jpg?v=2'
Fix now
Configure CDN to ignore query parameters for static assets, or add cache key rules to normalize.
Users see old content after deployment+
Immediate action
Purge the CDN cache for the changed URLs.
Commands
curl -X POST 'https://api.cloudflare.com/client/v4/zones/ZONE_ID/purge_cache' -H 'Authorization: Bearer TOKEN' -d '{"files":["URL"]}'
Verify purge: curl -I URL and ensure Cache-Control max-age restarted
Fix now
Implement content hashing in filenames to avoid manual purges.
API responses are being cached when they shouldn't be+
Large files, known launch events, video streams, software downloads
Cache invalidation
Wait for TTL or call purge API
Re-upload new file + call purge API or use versioned paths
Real-world examples
Cloudflare, Fastly default mode, AWS CloudFront
Bunny.net storage, KeyCDN, AWS CloudFront with S3 origin
Origin server load
Spiky — bursts on cache misses across many PoPs simultaneously
Minimal — origin only serves during your controlled push uploads
Key takeaways
1
The origin server's job is NOT to serve every user
it's to serve edge servers that haven't cached yet. Design your Cache-Control headers to keep origin traffic as low as possible for static assets.
2
Content-hashed filenames + long TTLs eliminate the cache invalidation problem for static assets entirely. Every senior frontend engineer uses this pattern. If you're not, start today.
3
Pull CDN is the right default for most web applications. Only switch to Push CDN when you have large files, predictable high-demand events, or an origin that can't absorb cache-miss spikes.
4
CDN cache hit ratio is your north-star metric. Below 80% means your CDN is expensive middleware, not a performance multiplier. The culprits are almost always
restrict traffic to CDN IPs, enable HTTPS between CDN and origin, and use WAF rules at the edge for application-layer filtering.
Common mistakes to avoid
4 patterns
×
Caching user-specific API responses at the CDN
Symptom
User A sees User B's account data, shopping cart, or personalised content — catastrophic privacy/security breach.
Fix
Always set 'Cache-Control: private, no-store' on any response that contains session-specific or user-specific data. Only cache responses that are identical for every user. Audit your Cache-Control headers on every API route before enabling CDN caching.
×
Not using cache-busting filenames, then setting long TTLs
Symptom
After a deployment, users continue seeing old JavaScript, CSS, or images for hours or days because the cached filename hasn't changed.
Fix
Configure your build tool to append a content hash to every asset filename (e.g. main.3f7a2b1c.js). With unique filenames per build, you can safely set a 1-year TTL. Old files simply stop being requested; no manual purging needed. This is the single most impactful CDN caching practice you can adopt.
CDN cache hit ratio is unexpectedly low (below 60%) even for static assets. Inspection reveals thousands of cache entries for the same file: image.jpg?v=1, image.jpg?v=2, image.jpg?cb=1234567.
Fix
Configure your CDN to either ignore all query parameters for static asset paths, or define a specific list of query parameters that should be included in the cache key. In Cloudflare this is 'Cache Key rules'; in Nginx+proxy_cache it's 'proxy_cache_key'. Strip or normalise tracking parameters (utm_source, fbclid, etc.) from the cache key entirely.
×
Not restricting origin access to CDN IP ranges
Symptom
Attackers bypass CDN DDoS protection by directly targeting origin IP. Origin becomes overwhelmed even though CDN is active.
Fix
Firewall your origin to accept traffic only from known CDN IP ranges. Most CDN providers publish a list of IP ranges. Use security groups or iptables to allow only those ranges, and deny all other inbound traffic.
INTERVIEW PREP · PRACTICE MODE
Interview Questions on This Topic
Q01SENIOR
Walk me through exactly what happens — at the network and application la...
Q02SENIOR
Our CDN cache hit ratio dropped from 92% to 41% overnight after a fronte...
Q03SENIOR
A product manager asks: 'Why can't we just cache all API responses at th...
Q01 of 03SENIOR
Walk me through exactly what happens — at the network and application layer — when a user in Singapore requests an image from a CDN-backed website hosted in London. Include DNS resolution, edge server behaviour, and what determines whether it's a cache hit or miss.
ANSWER
1. DNS resolution: Browser resolves the CDN domain. The authoritative DNS returns an IP via Anycast, directing to the nearest edge PoP (lowest latency). 2. Browser opens TCP/TLS connection to that edge IP. 3. HTTP request sent to edge server with the image URL. 4. Edge server checks its local cache using the URL (and any configured cache key) as the lookup. 5. If cache HIT: edge returns the image immediately. 6. If cache MISS: edge forwards request to the origin server (using the configured origin address). The origin returns the image with Cache-Control headers. Edge caches it based on those headers, then serves to user. 7. Response headers include CDN-specific cache status headers (e.g., X-Cache: HIT/MISS). The entire round-trip from Singapore to London adds ~150-200ms on a miss; a hit takes ~5-10ms.
Q02 of 03SENIOR
Our CDN cache hit ratio dropped from 92% to 41% overnight after a frontend deploy. What are the top three things you'd investigate first, and what specific CDN metrics or logs would you look at to diagnose the cause?
ANSWER
1. Check for cache key changes: Did the deploy introduce new query strings or change URL structures? Look in CDN logs for the top URLs now showing MISS with high request counts. Check if new tracking parameters (e.g., ?v=timestamp) were added to static assets, causing cache fragmentation. 2. Verify Cache-Control headers: Did the deploy change how headers are set on origin? Use curl -I against a static asset served through CDN. If Cache-Control now includes 'private' or 'no-store', the CDN won't cache. 3. Inspect for cache-busting mechanism: Did the deploy remove content hashes from filenames? If all assets now have the same filename (app.js) but with new content, and the old filename is still cached, new requests with the same URL might be served stale — or the deploy might have changed the URL to a new one that has no cache built up yet. Look at the distribution of cache MISS URLs: are they mostly new filenames? That's expected on a deploy with content hashes. If they're the same old URLs, then the cache was purged or headers changed. Specific metrics: 'Edge Cache Hit Ratio' per URL group, 'Origin Response Time' (spikes on MISS), 'Top MISS URLs' report in CDN analytics.
Q03 of 03SENIOR
A product manager asks: 'Why can't we just cache all API responses at the CDN to make the app faster?' How do you explain the risks, and what specific patterns (e.g. surrogate keys, Vary headers, stale-while-revalidate) would you propose to safely cache as much as possible without serving incorrect data?
ANSWER
Start by explaining the core risk: caching user-specific data would violate privacy. Then outline patterns: Surrogate keys (e.g., Surrogate-Key header in Fastly/Varnish) allow purging groups of cached responses (e.g., 'user:1234, product:5678'). Vary headers (Vary: Cookie, Authorization) tell CDN to cache different versions based on those headers, but this can lead to many cache entries. Stale-while-revalidate serves stale data immediately while fetching fresh in background — good for public data that changes slowly. Edge-side includes (ESI) can assemble pages from cached fragments, caching the public shell and injecting personalised blocks. Propose: (1) Public catalog API endpoints: cache at CDN with TTL 5-10 minutes. (2) User-specific endpoints: use private Cache-Control, no CDN caching. (3) For authenticated users accessing public endpoints, use Vary: Authorization but only if the number of unique auth tokens is small (e.g., server-to-server). (4) Consider surrogate-key-based purging on user data changes. Emphasise that any caching of personalised data must have a way to invalidate instantly when data changes (e.g., webhook from DB).
01
Walk me through exactly what happens — at the network and application layer — when a user in Singapore requests an image from a CDN-backed website hosted in London. Include DNS resolution, edge server behaviour, and what determines whether it's a cache hit or miss.
SENIOR
02
Our CDN cache hit ratio dropped from 92% to 41% overnight after a frontend deploy. What are the top three things you'd investigate first, and what specific CDN metrics or logs would you look at to diagnose the cause?
SENIOR
03
A product manager asks: 'Why can't we just cache all API responses at the CDN to make the app faster?' How do you explain the risks, and what specific patterns (e.g. surrogate keys, Vary headers, stale-while-revalidate) would you propose to safely cache as much as possible without serving incorrect data?
SENIOR
FAQ · 4 QUESTIONS
Frequently Asked Questions
01
What is the difference between a CDN edge server and an origin server?
The origin server is your actual application or web server — it holds the authoritative copy of all your content and handles dynamic requests. Edge servers are the CDN's distributed cache servers, placed in cities around the world. Edge servers serve cached copies of your content to nearby users so requests never need to travel back to the origin. The origin only gets involved on a cache miss — the first request for a piece of content at a given edge location.
Was this helpful?
02
Does using a CDN mean my website is always faster?
A CDN makes static assets (images, JS, CSS, fonts, videos) dramatically faster for geographically distant users. However, it doesn't automatically speed up dynamic content like personalised API responses or database queries — those still hit your origin. The performance gain depends heavily on your cache hit ratio and how well your Cache-Control headers are configured. A misconfigured CDN can actually slow things down by adding a network hop without caching anything useful.
Was this helpful?
03
How does a CDN know which edge server is 'closest' to a user?
Most CDNs use Anycast DNS routing. When a user's browser resolves your CDN domain, the DNS system returns the IP address of the edge PoP with the best network path to that user — not necessarily the physically nearest one, but the one reachable with the lowest latency given current internet routing conditions. Some CDNs supplement this with GeoDNS (routing by geographic IP lookup) or BGP Anycast, where the same IP address is announced from multiple PoPs and the internet's routing protocol automatically selects the best path.
Was this helpful?
04
Can I use a CDN to protect against DDoS attacks?
Yes, CDNs are excellent at DDoS mitigation. The distributed architecture absorbs large traffic volumes across many edge servers. CDN providers operate scrubbing centres that filter malicious traffic before it reaches your origin. Additionally, WAF rules at the edge block application-layer attacks. However, you must restrict origin access to CDN IP ranges only, otherwise attackers can bypass the CDN and target your origin directly.