Design CDN strategies that reduce latency, offload origin servers, and improve reliability. Covers cache hierarchies, cache invalidation, multi-CDN failover, edge computing, and the configuration patterns that prevent your CDN from serving stale content or bypassing your security controls.
Content delivery networks (CDNs) serve static and dynamic content from geographically distributed edge servers to reduce latency, improve availability, and offload origin infrastructure. This page details how to design, configure, and troubleshoot CDNs for edge delivery, focusing on the mechanics of content routing, caching, and update propagation.
Requests to a CDN begin at a user’s client, which resolves the domain to a CDN edge IP via DNS. The edge server, often running a reverse proxy such as NGINX or Traefik, receives the HTTP request and applies routing rules before forwarding to an origin or another edge tier.
A typical edge request includes:
Host: static.example.comUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)Accept: image/webp,*/*Accept-Encoding: gzip, deflate, brThe edge server evaluates routing rules based on:
/assets/js/main.js)?v=2.3.1)Accept: application/json)X-Forwarded-For or CF-IPCountry)Routing decisions are made via a configuration language or set of rules expressed in a common format. For example, NGINX uses location blocks with if conditions:
server {
listen 80;
server_name static.example.com;
location ~ ^/assets/(js|css)/.*\.(js|css)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
proxy_pass http://origin.example.com;
proxy_cache edge_cache;
proxy_cache_valid 200 1y;
proxy_cache_key "$scheme$request_method$host$request_uri";
}
location /api/ {
proxy_pass http://api.origin.com;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache api_cache;
proxy_cache_use_stale error timeout updating http_500;
}
location / {
try_files $uri $uri/ =404;
add_header Cache-Control "public, max-age=3600";
}
}
When a request hits an edge, the CDN evaluates a chain of matchers. The first matching rule determines the content source and caching behavior. If no rule matches, the request falls through to a default route.
Edge servers can use HTTP/2 or HTTP/3 to serve content with multiplexing and server push. A request for /index.html may trigger a push of /assets/css/main.css and /assets/js/app.js via Link headers:
HTTP/2 200 OK
Content-Type: text/html
Link: <https://static.example.com/assets/css/main.css>; rel=preload; as=style
Link: <https://static.example.com/assets/js/app.js>; rel=preload; as=script
Cache-Control: public, max-age=3600
CDNs use hierarchical caching, with edge, regional, and origin tiers. The edge tier holds frequently accessed content close to users. Each cache entry has a Cache-Control header, which may be set by the origin or enforced by the CDN.
A typical cache entry includes:
Cache-Control: public, max-age=300, s-maxage=600ETag: "abc123"Vary: Accept-Encoding, User-AgentAge: 45The CDN evaluates freshness:
Age + TTL ≤ max-age, the edge returns a 200 (Hit) from cache.Age + TTL > max-age, the edge issues a 304 Not Modified request to the origin and returns a 200 (Stale) response.For cache invalidation, the CDN supports two primary methods:
Cache-Control with max-age and s-maxage. The CDN refreshes content at the specified interval.PURGE request to the CDN edge. The PURGE method is defined in HTTP/1.1 and widely supported:PURGE /assets/js/main.js HTTP/1.1
Host: static.example.com
Authorization: Bearer cdn-secret-abc123
The CDN responds with:
200 OK if the entry was purged404 Not Found if the path does not exist410 Gone if the entry was purged and no longer availableThe CDN may also support BULK PURGE via a POST request with a list of paths:
POST /purge HTTP/1.1
Host: static.example.com
Content-Type: application/json
Authorization: Bearer cdn-secret-abc123
{
"paths": [
"/assets/css/main.css",
"/assets/js/app.js",
"/images/logo.png"
],
"strategy": "all"
}
The CDN supports three purge strategies:
all: Invalidate all caches along the hierarchy (edge, regional, origin).edge: Invalidate only edge caches.origin: Invalidate origin and all upstream caches.A common failure mode is stale content after deployment: a new version of main.js is deployed but the CDN does not purge it. The edge returns the old file until max-age expires or a manual purge is triggered.
CDNs serve content from two models: origin pull and origin push.
In origin pull, the CDN fetches content on demand from a source server. The origin is typically a web server (Apache, Nginx) or a storage backend (S3, Cloud Storage). When an edge server does not have a requested file, it issues a GET to the origin using a Origin header:
GET /assets/js/main.js HTTP/1.1
Host: static.example.com
Origin: http://origin.example.com
Accept: application/javascript
The origin responds with:
200 OKContent-Type: application/javascriptCache-Control: public, max-age=31536000ETag: "v2.3.1"The CDN caches the response and returns it to the client.
In origin push, content is proactively uploaded to the CDN. This is common for large assets like videos, fonts, or static site builds.
A typical push workflow uses curl or aws s3 cp:
aws s3 cp ./dist/ s3://cdn.example.com/ \
--recursive \
--acl public-read \
--cache-control "public, max-age=31536000" \
--content-type "application/javascript"
curl -X POST \
-H "Authorization: Bearer cdn-secret-abc123" \
-H "Content-Type: application/json" \
-d '{"paths": ["/assets/js/main.js", "/assets/css/main.css"]}' \
https://static.example.com/push
The CDN can validate the push via ETag or Last-Modified headers. If the origin sends ETag: "v2.3.1", the CDN checks for an existing ETag and only pushes if the content has changed.
A sharp edge: if the origin uses ETag but the CDN does not validate it, stale content can persist for months. The CDN may also support If-None-Match and If-Modified-Since in its origin pull logic.
Modern CDNs support edge compute, allowing logic to run at the edge. This includes:
Edge compute is typically implemented via a JavaScript runtime (like V8) that executes functions in response to incoming requests. A function is registered with the CDN via a configuration block:
{
"function": {
"name": "user-profile",
"handler": "index.handler",
"runtime": "nodejs18.x",
"path": "/profile/*",
"events": [
{
"type": "http",
"methods": ["GET"],
"headers": {
"X-Request-ID": "{{request_id}}"
}
}
],
"memory": 256,
"timeout": 5000,
"environment": {
"DATABASE_URL": "postgresql://db.example.com/users"
}
}
}
The function receives a request object and returns a response:
exports.handler = async (event) => {
const { path, headers, query } = event;
const userId = path.split('/')[2];
const db = await connect(process.env.DATABASE_URL);
const user = await db.query(
'SELECT name, avatar FROM users WHERE id = $1',
[userId]
);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'public, max-age=300'
},
body: JSON.stringify(user)
};
};
The edge server evaluates the function and returns the result. The function can also modify the request or response, including setting X-Cache headers:
HTTP/1.1 200 OK
Content-Type: application/json
X-Cache: HIT, from edge
X-Edge-Function: user-profile
Cache-Control: public, max-age=300
Edge compute introduces subtle failure modes:
timeout.environment block is misformatted.CDN failures often go unnoticed until users report slow or broken content.
Common failure modes:
ETag or Cache-Control is not set correctly.200 but does not update Last-Modified or ETag, so the CDN never refreshes the content.PURGE request returns 404 but the edge cache does not update the entry.500 but the CDN does not log the error or propagate it to clients.502 Bad Gateway responses.Observability is achieved through:
X-Cache, X-Edge-Function, and X-Response-Time.GET /health requests from the CDN to the origin, with expected response code and body.Cache-Control and Age headers to track hit/miss rates.A typical access log entry:
192.0.2.1 - - [2025-04-05T12:34:56Z] "GET /assets/js/main.js HTTP/1.1" 200 2314 "edge-cache" "HIT" "user-agent=Chrome/125" "response-time=42ms"
The CDN may expose a /status endpoint that returns metrics:
GET /status HTTP/1.1
Host: static.example.com
{
"cache": {
"hit_rate": 0.87,
"total_hits": 1248392,
"total_misses": 183721,
"average_age": 124.3,
"top_10_paths": [
"/assets/js/main.js",
"/index.html",
"/assets/css/main.css",
"/api/users",
"/images/logo.png"
]
},
"functions": {
"user-profile": {
"invocations": 1423,
"errors": 12,
"avg_duration_ms": 67.4
},
"api-proxy": {
"invocations": 891,
"errors": 3,
"avg_duration_ms": 112.1
}
},
"origin": {
"status": "healthy",
"last_check": "2025-04-05T12:35:00Z",
"latency_ms": 42
}
}
The CDN may also support X-Cache-Status header to indicate cache state:
X-Cache-Status: HIT, from edge, age=34, ttl=300
X-Cache-Status: MISS, from origin, age=0, ttl=3600
X-Cache-Status: STALE, from edge, updated, age=310, ttl=3600
These headers are critical for diagnosing performance issues and validating CDN behavior.
CDN configurations are typically versioned and deployed via CI/CD pipelines. A typical configuration file uses standard JSON or YAML:
cdn:
domain: static.example.com
edge:
cache:
default_ttl: 3600
max_age: 31536000
stale_while_revalidate: 86400
routing:
- path: "/assets/js/*.js"
cache_control: "public, max-age=31536000, immutable"
proxy_pass: "http://origin.example.com"
- path: "/api/*"
proxy_pass: "http://api.origin.com"
cache_control: "public, max-age=600"
- path: "/"
try_files: ["$uri", "$uri/"]
cache_control: "public, max-age=3600"
functions:
- name: user-profile
path: "/profile/*"
handler: "index.handler"
runtime: "nodejs18.x"
environment:
DATABASE_URL: "postgresql://db.example.com/users"
memory: 256
timeout: 5000
origin:
pull:
default_origin: "origin.example.com"
health_check_path: "/health"
health_check_interval: 30
health_check_timeout: 5
push:
storage: "s3://cdn.example.com"
cache_control: "public, max-age=31536000"
Deployment is done via git push to a CI pipeline that:
A common mistake is deploying a new configuration without purging the cache, leading to a mismatch between the new routing rules and the cached content.
The CDN can be tested using curl and diff:
curl -H "Host: static.example.com" http://cdn.example.com/ \
-H "Accept: application/json" \
-H "User-Agent: Mobile/1.0" \
--write-out "\n" \
--silent \
--output -
The output includes headers, X-Cache, X-Edge-Function, and Cache-Control, all of which can be verified against expected values.
This page was rewritten on 18 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy