Your user is in Tokyo. Your server is in Virginia. Every single request travels 10,000km across the Pacific Ocean and back — that's 200-400ms of pure network latency before your server even starts processing the request. Multiply that by dozens of assets on a page and you've got a site that feels sluggish no matter how fast your code is.
A CDN fixes this by putting copies of your content on servers around the world. That same user in Tokyo now hits a server in Tokyo — 5ms instead of 300ms. This guide covers how CDNs actually work under the hood, what they cache, how to invalidate stale content, and how to pick the right one for your project.
What Is a CDN?
A CDN (Content Delivery Network) is a network of servers distributed across the globe — called edge servers or PoPs (Points of Presence). These edge servers cache copies of your content so users get served from the location closest to them instead of your single origin server.
Think of it like a library system. Instead of one central library where everyone in the country has to drive to, you put smaller branches in every neighborhood. People grab books from their local branch. The central library only gets involved when a branch doesn't have what someone needs.
Major CDNs operate 200-300+ edge locations worldwide. Cloudflare has servers in 300+ cities. AWS CloudFront has 450+ PoPs. No matter where your user is, there's likely an edge server within 50km of them.
CDN at a glance:
Without CDN: User (Tokyo) → Origin Server (Virginia) → 10,000km round trip → ~300ms latency With CDN: User (Tokyo) → Edge Server (Tokyo) → 50km round trip → ~5ms latency Edge doesn't have it? → Edge fetches from Origin → caches it → next user gets it instantly
Why CDNs Matter
CDNs aren't just about speed — though that alone is worth it. Here's what a CDN gives you:
Speed
Physics doesn't negotiate. Light through fiber travels at about 200,000 km/s — a request from London to Sydney takes ~70ms minimum just for the speed-of-light round trip. CDNs eliminate this by serving from nearby. Cached responses arrive in 5-20ms instead of 200-400ms.
Reduced Origin Load
If 90% of requests hit the CDN cache, your origin handles 10x less traffic. This means cheaper infrastructure, fewer servers, and your origin stays responsive during traffic spikes. A viral post hits your CDN, not your $20/month VPS.
DDoS Protection
CDNs absorb volumetric attacks by distributing traffic across hundreds of edge nodes. An attacker would need to overwhelm Cloudflare's entire 300+ city network — not just your single server. Most CDNs include rate limiting and WAF (Web Application Firewall) too.
Reliability & Redundancy
If one edge server goes down, traffic routes to the next nearest one. If your origin goes down, CDNs can serve stale cached content while you fix things. Your users might not even notice the outage.
How a CDN Works (Step by Step)
Here's what happens when a user requests a resource from a CDN-backed site:
┌──────────┐ DNS resolves to ┌─────────────────┐
│ User │ ──────────────────────────→ │ Nearest Edge │
│ (Tokyo) │ │ Server (Tokyo) │
└──────────┘ └────────┬────────┘
│
Cache hit? ──→ YES → Return cached response (5ms)
│
NO
│
▼
┌─────────────────┐
│ Origin Server │
│ (Virginia) │
└────────┬────────┘
│
Origin responds
│
▼
Edge caches response
+ returns to user
(next request = cache hit)The flow in plain English:
- DNS resolution: User's browser resolves your domain. CDN's DNS returns the IP of the nearest edge server (anycast routing).
- Edge lookup: The edge server checks its local cache for the requested resource.
- Cache hit: If found and not expired, the edge serves it directly. Done in milliseconds.
- Cache miss: If not cached, the edge fetches it from your origin server, caches a copy, then returns it to the user.
- Subsequent requests: All future requests from that region hit the edge cache until TTL expires.
What CDNs Cache
CDNs can cache more than just images. Here's a breakdown of what's cacheable and what's not:
| Content Type | Cacheable? | Typical TTL | Notes |
|---|---|---|---|
| Static assets (JS, CSS) | Always | 1 year | Use content hashes in filenames for instant invalidation |
| Images & fonts | Always | 1 year | Immutable — hash in filename, cache forever |
| HTML pages | Often | 5 min – 1 hour | Short TTL or stale-while-revalidate for freshness |
| API responses (GET) | Sometimes | 30s – 5 min | Cache read-heavy endpoints, skip user-specific data |
| Video & audio | Always | 1 year | Large files benefit most from edge delivery |
| PDFs & downloads | Always | 1 week – 1 year | Version with URL path if updated frequently |
| POST/PUT/DELETE responses | Never | — | Mutations should never be cached |
| Authenticated responses | Rarely | — | Use Vary: Authorization or skip caching entirely |
The golden rule: if a resource has a content hash in its filename (like app.a3f8b2.js), cache it for a year. The hash changes when the content changes, so users always get the latest version without you worrying about stale caches.
CDN Architecture: Push vs Pull CDN
There are two fundamentally different approaches to getting content onto CDN edge servers. Each has tradeoffs that matter depending on your use case.
| Aspect | Pull CDN | Push CDN |
|---|---|---|
| How it works | CDN fetches from origin on first request, caches automatically | You upload content directly to CDN storage |
| Setup complexity | Point DNS at CDN — done | Build upload pipeline, manage storage |
| Cache population | Lazy — only cached when requested | Eager — available everywhere immediately |
| Origin dependency | Origin must be available for cache misses | Origin not needed after upload |
| Storage cost | Low — CDN manages cache eviction | Higher — you pay for all stored content |
| Best for | Websites, APIs, dynamic content | Large media files, software downloads, video |
| Invalidation | TTL-based or purge API | Re-upload new version |
| Examples | Cloudflare, CloudFront (default mode) | S3 + CloudFront, KeyCDN push zones |
When to use Pull: Most websites and APIs. You want the CDN to handle caching automatically. You change content frequently. You don't want to manage uploads.
When to use Push: Large static files that rarely change — video libraries, software installers, game patches. You want guaranteed availability without relying on an origin server.
Popular CDNs Compared
Here's how the major CDN providers stack up. Each has a sweet spot depending on your stack, budget, and scale.
| CDN | Edge Locations | Free Tier | Pricing | Best For | Standout Feature |
|---|---|---|---|---|---|
| Cloudflare | 300+ cities | Yes (generous) | Free – $20+/mo | Most projects | Free SSL, DDoS, WAF included |
| AWS CloudFront | 450+ PoPs | 1TB/mo free (12 months) | $0.085/GB | AWS-heavy stacks | Deep AWS integration, Lambda@Edge |
| Fastly | 90+ PoPs | Trial only | ~$0.12/GB | Dynamic content, APIs | Instant purge (<150ms global) |
| Akamai | 4,000+ locations | No | Enterprise pricing | High-traffic enterprise | Largest network, enterprise SLAs |
| Vercel Edge | Global (auto) | Included with Vercel | Based on plan | Next.js / Vercel apps | Zero-config for frameworks |
My recommendation for most developers: start with Cloudflare. The free tier handles small-to-medium traffic, setup takes 5 minutes (just change your nameservers), and you get DDoS protection, a WAF, and SSL certificates for free. If you outgrow it or need tighter AWS integration, CloudFront is the natural next step.
Cache Invalidation
"There are only two hard things in Computer Science: cache invalidation and naming things." The quote exists for a reason. Here are your options for telling a CDN to drop stale content:
1. Purge (Instant Invalidation)
Tell the CDN to delete a cached resource immediately. Every CDN has an API for this. Fastly purges globally in under 150ms. Cloudflare takes a few seconds. CloudFront can take 5-15 minutes for full global propagation.
# Cloudflare purge
curl -X POST "https://api.cloudflare.com/client/v4/zones/{zone_id}/purge_cache" \
-H "Authorization: Bearer {token}" \
-d '{"files":["https://example.com/styles.css"]}'
# Fastly purge by surrogate key
curl -X POST "https://api.fastly.com/service/{id}/purge/product-123" \
-H "Fastly-Key: {token}"2. TTL (Time-To-Live)
Set an expiration time. After TTL expires, the edge refetches from origin on next request. Simple and predictable, but you live with stale content until TTL expires.
Cache-Control: public, max-age=3600 → Stale after 1 hour Cache-Control: public, max-age=86400 → Stale after 1 day Cache-Control: public, max-age=31536000 → Stale after 1 year (use with hashed filenames)
3. Versioned URLs
Put a content hash or version number in the URL. When content changes, the URL changes — the old cached version is irrelevant because nothing requests it anymore. This is the cleanest invalidation strategy.
/assets/app.a3f8b2c1.js → hash changes when code changes /assets/styles.v2.css → version bump forces new URL /api/v2/users → API versioning in path
4. Stale-While-Revalidate
Serve stale content immediately while fetching fresh content in the background. Users get instant responses, and the cache updates for the next request. Best of both worlds.
Cache-Control: public, max-age=60, stale-while-revalidate=3600 // Serves cached content for 60s // After 60s: serves stale + refetches in background for up to 1 hour // After 1 hour of stale: full cache miss
CDN for API Responses
Caching static assets is obvious. But CDNs can also cache API responses — and this is where things get interesting (and tricky). Done right, you can serve API responses in 5ms instead of 200ms without hitting your database.
Cache-Control for APIs
Set appropriate Cache-Control headers on GET responses. Read-heavy endpoints (product listings, blog posts, public data) cache well. Write-heavy or user-specific endpoints should not.
// Public product listing — cache for 5 minutes GET /api/products Cache-Control: public, max-age=300, stale-while-revalidate=600 // User-specific data — don't cache at CDN GET /api/me/orders Cache-Control: private, no-store // Rarely changing reference data — cache for 1 hour GET /api/countries Cache-Control: public, max-age=3600
Surrogate Keys (Cache Tags)
Tag cached responses with identifiers so you can purge all related content at once. Update a product? Purge everything tagged "product-123" — the listing page, the detail page, the search results that include it.
// Response header (Fastly, Cloudflare Enterprise)
Surrogate-Key: product-123 category-electronics homepage-featured
// Purge everything related to product 123:
curl -X POST "https://api.fastly.com/service/{id}/purge/product-123"Vary Header
The Vary header tells CDNs to cache separate versions based on request headers. Without it, a CDN might serve a JSON response to someone who requested HTML, or an English page to a French user.
// Cache different versions based on Accept header Vary: Accept // Stores separate cached versions for: // Accept: application/json → JSON response // Accept: text/html → HTML response // Cache per language Vary: Accept-Language // ⚠️ Never use: Vary: * (disables caching entirely) // ⚠️ Avoid: Vary: Cookie (creates a unique cache per user = useless)
Performance Impact: Before vs After CDN
Here are realistic latency numbers showing what a CDN actually does to response times. These are based on typical single-origin deployments vs CDN-backed setups.
| Scenario | Without CDN | With CDN | Improvement |
|---|---|---|---|
| Static asset (same region) | 50ms | 5ms | 10x faster |
| Static asset (cross-continent) | 300ms | 8ms | 37x faster |
| HTML page (dynamic, cached) | 250ms | 15ms | 16x faster |
| API response (cached GET) | 200ms | 12ms | 16x faster |
| API response (cache miss) | 200ms | 220ms | Slightly slower (first request) |
| Image (1MB, cross-continent) | 1.2s | 80ms | 15x faster |
| TTFB (Time to First Byte) | 400ms | 25ms | 16x faster |
| Full page load (global average) | 3.2s | 0.8s | 4x faster |
Notice the "cache miss" row — the first request is actually slightly slower because it goes through the CDN edge AND then to origin. But every subsequent request from that region is served from cache. The tradeoff is worth it for any resource requested more than once.
The "full page load" improvement is 4x rather than 16x because pages include uncacheable elements (personalized content, fresh API calls). But even 4x faster is the difference between a bouncy user and a happy one.
Common CDN Mistakes
I've seen (and made) these mistakes enough times to know they're worth calling out explicitly:
1. Caching responses with Set-Cookie
If your origin sends Set-Cookie headers, most CDNs won't cache the response (good) — but some configurations let it through. One user's session cookie gets served to thousands of other users. Always ensure responses with Set-Cookie bypass the cache.
2. Not setting Cache-Control headers
Without explicit Cache-Control headers, CDNs use their own defaults — which vary wildly between providers. Some cache for hours, some don't cache at all. Always be explicit about what should and shouldn't be cached.
3. Using Vary: Cookie
This creates a unique cache entry for every unique cookie value. Since every user has different cookies, you effectively disable caching entirely. Your CDN becomes an expensive proxy. Use Vary: Accept-Encoding or Vary: Accept-Language instead.
4. Caching error responses
Your origin returns a 500 error during a brief outage. The CDN caches it. Now every user sees the error page for the next hour. Configure your CDN to never cache 4xx/5xx responses, or set very short TTLs on error pages.
5. Forgetting about cache-busting for deploys
You deploy new JavaScript but the old version is cached at the CDN for another 6 hours. Users see broken pages because old JS tries to talk to new APIs. Always use content hashes in filenames or purge on deploy.
6. Over-caching personalized content
User A's dashboard gets cached and served to User B. This is a security incident. Never cache responses that contain user-specific data unless you're using Vary headers correctly or separating content into cacheable (shell) and non-cacheable (user data) layers.
CDN Best Practices
Follow these to get the most out of your CDN without shooting yourself in the foot:
1. Use content hashes for static assets
Name files like app.a3f8b2.js instead of app.js. Set max-age to 1 year. When you deploy new code, the filename changes automatically and users get the new version. No purging needed.
2. Set explicit Cache-Control on every response
Don't rely on CDN defaults. Be intentional: static assets get long max-age, HTML gets short TTL with stale-while-revalidate, API mutations get no-store. Document your caching strategy.
3. Use stale-while-revalidate for HTML
Users get instant page loads from cache while the CDN fetches fresh content in the background. Set a short max-age (60s) with a longer stale-while-revalidate (3600s). Pages feel instant but stay reasonably fresh.
4. Separate cacheable from non-cacheable content
Serve your page shell (HTML layout, navigation) from CDN cache. Load personalized content (user name, cart count) via client-side API calls. This way 90% of the page is cached and only the dynamic bits hit your server.
5. Monitor your cache hit ratio
Aim for 90%+ cache hit ratio on static assets. If it's lower, check your Cache-Control headers, Vary usage, and whether query strings are fragmenting your cache. Most CDN dashboards show this metric prominently.
6. Purge on deploy, not on schedule
Integrate cache purging into your CI/CD pipeline. When you deploy, purge affected resources. Don't rely on short TTLs as your invalidation strategy — you'll either serve stale content or hammer your origin with unnecessary revalidation requests.
7. Enable compression at the edge
Let your CDN handle gzip/brotli compression. This offloads CPU from your origin and ensures every response is compressed regardless of your application's configuration. Most CDNs do this automatically for text-based content types.
8. Test from multiple locations
Your CDN might work perfectly from your office but be misconfigured in another region. Use tools like curl with different PoP-specific URLs, or services like WebPageTest to verify performance from London, Tokyo, São Paulo — not just your local dev machine.
Frequently Asked Questions
What is a CDN?
A CDN (Content Delivery Network) is a globally distributed network of servers that caches and delivers content from locations physically closer to users. Instead of every request traveling to your origin server, the nearest CDN edge server responds with cached content, reducing latency and improving load times.
How does a CDN improve website speed?
A CDN reduces latency by serving content from edge servers located near the user. Instead of a request traveling 10,000km to your origin, it travels to the nearest edge (often under 50km). This can reduce page load times from 2-3 seconds to under 200ms for cached content.
What is the difference between a push CDN and a pull CDN?
A pull CDN fetches content from your origin server on the first request and caches it automatically. A push CDN requires you to manually upload content to the CDN's servers. Pull CDNs are easier to set up and handle cache management automatically. Push CDNs give you more control and work better for large, rarely-changing files.
What is cache invalidation in a CDN?
Cache invalidation is removing or updating stale content from CDN edge servers. Common methods include purging specific URLs via API, setting TTL headers, using versioned URLs with content hashes, and stale-while-revalidate which serves stale content while fetching fresh content in the background.
Can a CDN cache API responses?
Yes. CDNs can cache API responses using Cache-Control headers, surrogate keys for targeted invalidation, and the Vary header to cache different response versions based on request headers. This works well for read-heavy APIs where responses don't change on every request.
Does a CDN protect against DDoS attacks?
Yes. CDNs absorb DDoS traffic by distributing it across hundreds of edge servers worldwide. The attack hits the CDN's massive network instead of your single origin server. Most CDNs also include rate limiting, bot detection, and Web Application Firewalls (WAF) as additional protection layers.
Which CDN should I use?
Cloudflare is the best starting point for most developers — generous free tier, easy setup, and solid performance. AWS CloudFront integrates well if you're already on AWS. Fastly offers real-time purging for dynamic content. Vercel Edge Network is ideal for Next.js apps. Akamai is enterprise-grade for high-traffic global sites.
How much does a CDN cost?
Cloudflare offers a free tier that handles most small-to-medium sites. AWS CloudFront charges per GB of data transfer (starting around $0.085/GB). Fastly charges based on requests and bandwidth. For most projects, CDN costs are minimal compared to the performance and reliability benefits.
Related Articles
Conclusion
A CDN is one of the highest-impact, lowest-effort performance improvements you can make. Point your DNS at Cloudflare, set proper Cache-Control headers, use content hashes in your asset filenames, and you've just made your site 4-10x faster for users worldwide.
The key takeaways: cache static assets aggressively with long TTLs and hashed filenames. Use stale-while-revalidate for HTML pages. Be careful with API caching — only cache GET requests for public data, and use Vary headers correctly. Monitor your cache hit ratio and purge on deploy.
Start with a pull CDN (Cloudflare free tier), get comfortable with cache headers, then explore advanced features like edge computing, surrogate keys, and custom cache rules as your needs grow. Your users halfway around the world will thank you.
