Content Delivery Networks: A Practical CDN Deployment Guide
A content delivery network is easy to switch on. The difficult part is deciding what the edge may cache, what must always return to the application, and how you will recover when those rules are wrong. This guide builds that operating model from the first request trace through production cutover.
The examples use www.example.com, origin.example.net, and documentation addresses. Replace them with approved values. Commands that inspect a public endpoint are safe to run from an ordinary workstation. Configuration and DNS changes belong in a reviewed change window.
What a CDN actually changes
Without a CDN, a browser resolves your hostname and connects to the origin or its load balancer. With a CDN, DNS leads the browser to an edge location selected by the provider. A cached response can return from that edge. A miss, bypass, or uncacheable request continues to the origin, often through an upper-tier cache or origin shield.
This shortens some network paths and reduces repeated origin work. It does not repair a slow database query, make an uncached API fast, or remove the need for origin capacity. The first useful diagram for a CDN project is therefore a request map, not a vendor comparison.
| Traffic | Starting policy | Reason |
|---|---|---|
| Hashed CSS, JavaScript, fonts, and images | Long shared-cache lifetime; new URL for every changed object | The filename identifies the content, so an old object can remain cached safely. |
| HTML that is identical for everyone | Short shared-cache lifetime or revalidation | Small freshness windows can absorb traffic without hiding a release for long. |
| Personalized HTML and authenticated APIs | private, no-store until a reviewed design proves otherwise | A shared cache must not mix one user's response with another user's request. |
| Public GET API | Cache only named routes with a deliberate key and TTL | Query strings, headers, authorization, language, and tenant identity can change the representation. |
| Video and large downloads | Test range requests, signed access, chunking, and origin load | Partial responses and authorization require more than a generic static-file rule. |
| Uploads, checkout, login, and administration | Bypass cache and restrict at the edge | These are state-changing or sensitive workflows, not cache candidates. |
Capture a baseline before touching DNS
Measure from the places that matter to the application. One fast result from the office says little about customers on a different ISP or continent. Record the current DNS answers, TLS certificate, response headers, body checksum, timing, status, and origin load.
date -u
dig +short A www.example.com
dig +short AAAA www.example.com
dig +short CNAME www.example.com
curl -sS -D /tmp/origin-headers.txt \
-o /tmp/origin-body.bin https://www.example.com/
sha256sum /tmp/origin-body.bin
sed -n '1,40p' /tmp/origin-headers.txt
curl -sS -o /dev/null \
-w 'code=%{http_code} remote=%{remote_ip} dns=%{time_namelookup} connect=%{time_connect} tls=%{time_appconnect} ttfb=%{time_starttransfer} total=%{time_total}\n' \
https://www.example.com/
Run several samples rather than treating one request as a benchmark. Keep application and load-balancer metrics beside the client trace. A faster time-to-first-byte paired with a rising error rate is not a successful rollout.
Unsafe: curl -k disables certificate verification. It can help isolate a certificate problem during a controlled investigation, but it must not be used as the acceptance test or embedded in monitoring.
Make the origin's caching intent explicit
A CDN can override origin headers, but the cleanest design starts with honest HTTP responses. These directives carry different meanings:
| Directive | Practical meaning | Common mistake |
|---|---|---|
public | A shared cache may store the response when the other conditions permit it. | Adding it to a response that contains account or tenant data. |
private | The response is intended for a private cache, usually one user's browser. | Assuming it means encrypted or confidential storage. |
no-store | Caches must not store the request or response. | Replacing it with no-cache, which permits storage with revalidation. |
no-cache | The response may be stored but must be validated before reuse. | Reading the name as “never store.” |
max-age | Freshness lifetime for a browser and, unless overridden, shared caches. | Giving unversioned assets a year-long lifetime. |
s-maxage | Shared-cache freshness lifetime that takes precedence over max-age. | Forgetting that the browser and edge can then have different lifetimes. |
stale-while-revalidate | Allows a stale response while the cache refreshes it in the background. | Using it where even a brief stale response is unacceptable. |
stale-if-error | Allows stale content during eligible upstream failure. | Serving stale account, price, legal, or safety information without approval. |
Nginx example for versioned static assets
# Use this only when filenames contain a content hash.
location ~* ^/assets/.+\.[0-9a-f]{8,}\.(?:css|js|png|jpg|jpeg|gif|webp|svg|woff2)$ {
try_files $uri =404;
add_header Cache-Control "public, max-age=31536000, immutable" always;
}
# The application should set route-specific policy for HTML and APIs.
location / {
try_files $uri $uri/ /index.php?$query_string;
}
Do not apply the year-long rule to app.js, logo.png, or another reusable filename unless deployment creates a new URL whenever the bytes change. Purging is a recovery tool; content-addressed filenames are the normal release mechanism.
Application response examples
# Public HTML with a five-minute shared lifetime
Cache-Control: public, max-age=0, s-maxage=300, stale-while-revalidate=30
# Account page or authenticated API response
Cache-Control: private, no-store
# Versioned static object
Cache-Control: public, max-age=31536000, immutable
Design the cache key before chasing hit ratio
The cache key decides which requests may share one object. A key that includes every cookie and query parameter creates thousands of nearly empty cache variants. A key that ignores a meaningful language, tenant, currency, device, or authorization signal can serve the wrong representation.
Start with scheme, host, normalized path, and only the query parameters that change the response. Remove tracking parameters only after proving the application does not use them. Forwarding a value to the origin and including it in the cache key are separate decisions on several CDN platforms.
Example request:
https://www.example.com/products/42?color=blue&utm_source=newsletter
Possible reviewed key:
https | www.example.com | /products/42 | color=blue
Never ignore without evidence:
tenant, account, language, currency, authorization, preview, signed-access fields
Unsafe: a “cache everything” rule on all GET requests can store personalized HTML, error pages, redirects, and authenticated responses. If a provider offers a force-cache mode, scope it to known public paths and test the exact exclusion order.
Keep compression and variants under control
Compression changes the representation even when the URL stays the same. Most current CDNs can negotiate Brotli or gzip at the edge, but the origin, CDN, and browser must agree on how variants are stored and identified. Check Content-Encoding, Vary, body integrity, and object size with and without an Accept-Encoding request header.
URL='https://www.example.com/assets/app.9f31c2ab.js'
curl -sS --compressed -D /tmp/compressed.headers \
-o /tmp/compressed.body "$URL"
curl -sS -H 'Accept-Encoding: identity' \
-D /tmp/identity.headers -o /tmp/identity.body "$URL"
grep -Ei '^(HTTP/|content-encoding:|content-length:|vary:|etag:|cache-control:)' \
/tmp/compressed.headers /tmp/identity.headers
wc -c /tmp/compressed.body /tmp/identity.body
curl --compressed asks for supported compression and transparently decodes the downloaded body, so the saved byte count is not the wire-transfer size. Use CDN logs, browser developer tools, or a packet-aware measurement when transfer size is the question.
Every new Vary field can multiply cache entries. Vary: Accept-Encoding is common. Vary: Cookie often destroys reuse because cookie values differ widely. Vary: User-Agent can create an uncontrolled number of variants. Prefer a small normalized device or content capability in the provider's cache key when the response truly differs.
CORS needs similar discipline. If the application returns a different Access-Control-Allow-Origin value for each approved origin, its cache design must prevent one caller receiving another caller's policy. Test allowed and denied origins explicitly. Never reflect an arbitrary Origin request header while also allowing credentials.
Keep the origin private enough to matter
A visitor who can resolve or guess the origin address may bypass edge rate limits, WAF rules, bot controls, and DDoS absorption. Protect the origin with the strongest mechanism the platform supports: private service connectivity, an origin access identity, mutual TLS, authenticated origin pulls, a rotated secret header checked by the origin, or maintained provider address ranges.
IP allowlists alone need an owner and an update process. CDN networks change. A stale allowlist can either block legitimate fills or leave an abandoned range trusted.
# Inventory exposure from an approved external probe.
dig +short origin.example.net
nmap -Pn -p 80,443 origin.example.net
# Confirm the public hostname and origin do not accidentally expose
# different applications for an untrusted Host header.
curl -sS -D - -o /dev/null \
--resolve www.example.com:443:192.0.2.10 \
https://www.example.com/
The --resolve test deliberately connects to a chosen address while preserving TLS SNI and the HTTP host. Run it only against systems you own or are authorized to test.
Close cache-poisoning and cache-deception paths
A cache turns one upstream response into many downstream responses. That multiplication is valuable for performance and dangerous when an attacker can influence the stored object. Keep untrusted forwarding headers, rewritten hostnames, method overrides, and ambiguous paths out of the key and origin request unless the application needs them.
Normalize or reject duplicate slashes, dot segments, encoded delimiters, unexpected ports, and unrecognized hostnames consistently at the edge and origin. If the two layers interpret a path differently, an attacker may persuade one layer that a private page looks like a static file. Do not classify cacheability from a filename extension alone when application routing can serve account content at that path.
# Use a non-sensitive test route and compare edge/origin interpretation.
for path in \
'/public/test.css' \
'/public//test.css' \
'/public/../public/test.css' \
'/account/test.css'; do
printf '\npath=%s\n' "$path"
curl --path-as-is -sS -D - -o /dev/null \
"https://www.example.com${path}" \
| grep -Ei '^(HTTP/|location:|cache-control:|age:|x-cache:|cf-cache-status:)'
done
These requests can appear in security monitoring, and the last path must use a dedicated test account or a route that carries no personal data. The expected outcome is defined by your router. What matters is that the CDN and origin agree and that private content never receives a shared-cache policy.
Do not trust a client-supplied X-Forwarded-Host, X-Original-URL, or similar override merely because the CDN forwards it. Strip unneeded values at the edge, set trusted forwarding metadata yourself, and make the origin accept only the intended host and proxy path. Add automated tests whenever a rule, framework router, or reverse proxy changes.
Use shielding to prevent a miss storm
With independent edge caches, a new release or purge can make many locations request the same object from the origin at once. Tiered caching or origin shielding sends those misses through a smaller upper tier. Request collapsing can reduce duplicate fills further.
This changes the failure path. Monitor the edge, shield, and origin separately. A shield selected far from the origin can add latency to every pass and miss. A purge of the entire site can still create a large refill wave.
Set up TLS, DNS, and cutover without guessing
Add the hostname to the CDN, complete domain validation, and wait for the edge certificate before changing public DNS. Confirm supported TLS versions, HTTP/2 and HTTP/3 policy, IPv6, redirects, HSTS ownership, and certificate renewal alerts. Keep the origin certificate valid even when only the CDN should reach it.
# Before cutover
dig +short CNAME cdn-validation.example.com
openssl s_client -connect cdn.example.net:443 \
-servername www.example.com -verify_return_error </dev/null
# Test the CDN endpoint while retaining the production Host and SNI.
curl -sS -D /tmp/cdn-headers.txt -o /tmp/cdn-body.bin \
--resolve www.example.com:443:192.0.2.20 \
https://www.example.com/
sha256sum /tmp/cdn-body.bin
diff -u /tmp/origin-headers.txt /tmp/cdn-headers.txt || true
A header diff is evidence to review, not an automatic failure. The CDN will legitimately add cache, trace, and timing headers. Investigate lost security headers, changed cookies, unexpected redirects, compressed-body mistakes, and a changed content type.
Lower the existing DNS TTL ahead of the approved window, wait at least the previous TTL, then confirm major resolvers see the lower value. Keep the previous target available through the rollback window. Apex domains may require an ALIAS, ANAME, flattening, or provider-managed address rather than a normal CNAME.
Prove hits, misses, bypasses, and ranges
Vendor headers differ, but the test method is the same. Make two normal requests, inspect Age and provider cache-status headers, change a key component, test an excluded route, and verify the origin counters agree.
URL='https://www.example.com/assets/app.9f31c2ab.js'
for request_number in 1 2 3; do
printf '\nrequest=%s\n' "$request_number"
curl -sS -D - -o /dev/null "$URL" \
| grep -Ei '^(HTTP/|age:|cache-control:|vary:|etag:|last-modified:|cf-cache-status:|x-cache:|x-cache-hits:|server-timing:)'
done
# Query-string behavior
curl -sS -D - -o /dev/null "${URL}?utm_source=test"
# Range behavior for a large object
curl -sS -D - -o /dev/null \
-H 'Range: bytes=0-1023' \
https://www.example.com/downloads/large-file.bin
# Private route must not become a shared-cache hit
curl -sS -D - -o /dev/null \
https://www.example.com/account/
Do not send real session cookies into shared test logs. Use a dedicated test account and redact authorization, cookie, signed-URL, and personal fields from evidence.
Release with versioned URLs; invalidate narrowly
A release should publish immutable assets first, deploy HTML that points to their new names, and leave the prior assets available during rollback. This avoids a global purge and lets old HTML continue loading its matching files.
When an object must be removed early, invalidate the smallest path, prefix, host, or cache tag that solves the problem. Confirm whether the provider treats query variants, case, custom keys, and wildcard paths the way you expect.
# Amazon CloudFront example: invalidate one known object.
aws cloudfront create-invalidation \
--distribution-id E1234567890ABC \
--paths '/index.html'
# Azure Front Door Standard/Premium example: purge one path.
az afd endpoint purge \
--resource-group cdn-production \
--profile-name public-edge \
--endpoint-name website \
--domains www.example.com \
--content-paths '/index.html'
Unsafe at scale: invalidating /*, pressing “purge everything,” or clearing every cache tag can send the full audience back to the origin. The command remains valid for a genuine site-wide exposure or corruption event, but first confirm origin headroom, rate limits, refill order, and rollback.
Do not confuse a CDN with every adjacent edge product
| Capability | What it contributes | What it does not guarantee |
|---|---|---|
| Authoritative DNS | Directs the hostname toward the CDN or traffic manager. | That the response is cached or the origin is protected. |
| Load balancer | Distributes requests among origins or application instances. | Global edge caching close to users. |
| WAF and bot controls | Inspect or rate-limit eligible edge traffic. | Protection when the origin remains directly reachable. |
| DDoS protection | Absorbs or filters supported attack traffic. | Application correctness or unlimited capacity. |
| Public JavaScript library CDN | Hosts common third-party open-source assets. | A controlled CDN for your whole site, API, logs, purge, or origin. |
| Internet exchange | Lets participating networks exchange traffic directly. | That every connected network sells a CDN service or serves your object from that city. |
Build a shortlist by workload, not by logo count
The providers below are starting points for evaluation, not a ranking. Contract, price, points of presence, cache behavior, logs, support, security controls, purge model, origin connectivity, data processing, and actual performance vary by plan and region.
| Provider or service | Useful evaluation angle | Check before selection |
|---|---|---|
| Akamai CDN | Large distributed platform, media and enterprise delivery | Configuration model, contract, logs, purge, security integration, and support boundary |
| Cloudflare CDN | Integrated proxy, DNS, cache, security, and edge controls | Plan-specific cache keys, logs, rules, origin protection, and data location needs |
| Amazon CloudFront | AWS-integrated origins, policies, signed access, functions, and shielding | Cache/origin-request policies, invalidation, logging cost, origin identity, and AWS egress |
| Fastly CDN | Programmable edge behavior, fast purge, shielding, and detailed delivery control | VCL or Compute skills, service limits, logging, key design, purge authentication, and support |
| Google Cloud CDN | Google Cloud load-balancer integration, signed access, and backend services/buckets | Cache mode, URL map, key policy, private origins, invalidation, and project cost model |
| Azure Front Door Standard/Premium | Azure global application delivery, caching, routing, and security | Tier, rules engine, origin health, purge behavior, private link needs, and migration status |
| Imperva CDN | Application delivery combined with security controls | Cache behavior, origin access, logs, false-positive operations, regional reach, and contract |
| bunny.net CDN | Self-service delivery, storage integration, and straightforward regional pricing | Required regions, log detail, purge, security, support, compliance, and origin controls |
| CDN77 | Web, software, and video delivery | Actual audience coverage, cache policy, logs, traffic commitment, security, and support |
| Gcore CDN | CDN, media, edge, and security options | Plan-specific network reach, rules, observability, data handling, and support |
| CacheFly | Content and large-object delivery | Audience tests, cache key, origin shield, logs, purge model, and commercial terms |
| KeyCDN | Self-service pull zones and static delivery | Feature fit, regional tests, logs, security, purge, support, and compliance |
For shared open-source front-end libraries, jsDelivr and cdnjs solve a narrower problem. They are not substitutes for controlling your own hostname, origin, cache policy, access logs, and purge process.
Use multi-CDN only when one CDN is not enough
Two providers can reduce dependence on one control plane or network, but they also create two cache languages, two log formats, two security policies, and two incident paths. The hard part is not adding a second CNAME. It is keeping both providers behaviorally equivalent while traffic moves between them.
Choose the steering layer deliberately. DNS steering is widely supported but follows resolver and TTL behavior. Anycast or provider-specific traffic management has different failure boundaries. Application redirects add another request and may not help when the primary hostname is unavailable. Whatever the method, measure how quickly a failed provider leaves service and how much cold-cache traffic reaches the origin after the move.
Use the same immutable asset URLs, cache intent, private-route exclusions, origin authentication, TLS policy, security headers, signed-access rules, and release identifiers on both paths. Send logs to one normalization pipeline and retain each provider's trace ID. A health check that fetches only /health can declare a CDN healthy while customer objects, certificate delivery, or origin fills are broken, so test representative public and uncached paths.
Multi-CDN is justified when measured availability, geography, contractual independence, capacity, media delivery, or regulatory requirements outweigh that extra work. For a small site with one origin and no 24-hour operations team, a well-tested single CDN plus a documented DNS bypass may recover faster than two partially maintained configurations.
Know which familiar service names require action
| Name found in an inventory | Current operating decision |
|---|---|
| Edgio, EdgeCast, or Limelight delivery | Do not select it for a new deployment. Akamai acquired selected Edgio customer contracts and patent rights, not the Edgio network or technology, before Edgio's CDN ceased operation in January 2025. Confirm where any remaining hostname now terminates. |
| Azure CDN from Edgio | Retired on 15 January 2025. Verify that profiles and DNS have moved to a supported service. |
| Azure Front Door (classic) | Retires on 31 March 2027. New work should use Front Door Standard or Premium. |
| Azure CDN Standard from Microsoft (classic) | Retires on 30 September 2027. Plan migration to Front Door Standard or Premium. |
| “Free CDN” in a feature list | Separate a vendor's free plan from a public library CDN. Confirm quotas, logs, support, security, terms, and whether your own hostname and origin are supported. |
Evaluate CDN reach in India with measurements
DE-CIX India currently operates exchanges in Mumbai, Delhi, Chennai, Kolkata, Hyderabad, and Bengaluru. Its connected-network directory is useful evidence of interconnection, but it is not a product catalogue. The directory can also retain names during a network transition, so confirm service status with the network and CDN provider.
| Network shown in the DE-CIX India directory | ASN | Locations currently listed |
|---|---|---|
| Akamai Technologies | AS20940 | Mumbai, Delhi, Chennai, Kolkata |
| Amazon.com | AS16509 | Mumbai, Delhi, Chennai, Kolkata |
| Cloudflare | AS13335 | Mumbai, Delhi |
| Fastly | AS54113 | Mumbai |
| AS15169 | Mumbai, Delhi, Chennai | |
| Microsoft | AS8075 | Mumbai, Delhi, Chennai |
| CacheFly | AS30081 | Mumbai |
| G-Core Labs | AS199524 | Mumbai |
| Imperva | AS19551 | Mumbai |
| Netflix | AS2906 | Mumbai, Delhi |
| Meta | AS32934 | Mumbai, Delhi, Chennai, Kolkata, Hyderabad |
| Apple | AS714 | Mumbai |
This compact view was checked against the live directory on 19 August 2026. Netflix, Meta, and Apple are examples of content networks, not general CDN products offered to every website owner. Port capacity is deliberately omitted because it changes and does not tell you how your users will be routed. Use the live DE-CIX India connected-network directory for the full list, then test actual customer ISPs and cities.
# Resolve and record the edge selected from each approved test location.
dig +short A www.example.com
dig +short AAAA www.example.com
# Trace path quality without assuming the final responding router is the cache.
mtr -rwzc 20 www.example.com
# Capture repeatable HTTP timing and the selected remote address.
curl -sS -o /dev/null \
-w 'remote=%{remote_ip} code=%{http_code} ttfb=%{time_starttransfer} total=%{time_total}\n' \
https://www.example.com/assets/test-object.bin
Watch the signals that expose a bad CDN rule
Build one view that joins client experience, CDN delivery, and origin health. At minimum, follow request volume, hit/miss/bypass state, status codes, time to first byte, bytes served, origin requests, origin egress, shield behavior, purge activity, TLS errors, WAF decisions, and cost. Break results down by hostname, path class, country or region, device family, and release when those dimensions are available and privacy-approved.
A rising hit ratio is not automatically good. It may mean that an over-broad rule is caching something that should be private. Keep synthetic tests for known public, private, missing, redirected, ranged, compressed, and error responses.
Troubleshoot from the response back to the origin
| Symptom | Evidence to compare | Likely boundary |
|---|---|---|
| Object never becomes a hit | Cache-Control, Vary, cookies, query key, status, size, provider cache status | Uncacheable origin response, fragmented key, rule order, or eviction |
| Wrong language, currency, or tenant | Request dimensions, key definition, response variants, cookies, authorization | A meaningful value is missing from the key or a private route was cached |
| Release still shows old content | Asset URL, HTML URL, TTL, purge target, query variants, shield state | Reusable filenames, incomplete invalidation, browser cache, or stale policy |
| Origin overload after purge | Purge event, edge/shield misses, request collapsing, origin queue and saturation | Broad invalidation or insufficient shield/refill control |
| Intermittent 502, 503, or 504 | CDN trace ID, origin health, connect/TLS time, timeout, keepalive, deployment events | Origin connection, capacity, health checks, or timeout mismatch |
| Large download fails | Range request, 206, Content-Range, object size, timeout, compression | Range handling, fill limit, origin support, or transfer timeout |
| Direct origin remains reachable | DNS history, certificates, firewall, object-store policy, origin authentication | Missing private connectivity or origin access enforcement |
Roll back without creating a second incident
Rollback is usually a DNS or traffic-manager change plus restoration of the prior origin policy. Keep the origin capable of taking the expected load. Do not delete the CDN configuration, certificate, logs, or prior DNS target until the rollback window closes.
# Before rollback, record current answers and edge evidence.
date -u
dig www.example.com A +noall +answer
dig www.example.com AAAA +noall +answer
curl -sS -D /tmp/pre-rollback-headers.txt \
-o /dev/null https://www.example.com/
# Apply the reviewed DNS or traffic-manager rollback outside this shell.
# Then verify from multiple resolvers and networks.
dig @1.1.1.1 www.example.com A +noall +answer
dig @8.8.8.8 www.example.com A +noall +answer
curl -sS -o /dev/null \
-w 'remote=%{remote_ip} code=%{http_code} total=%{time_total}\n' \
https://www.example.com/
If the incident is limited to one cache rule, a scoped configuration rollback is safer than bypassing the entire CDN. If the edge is unavailable, the origin must still enforce authentication, authorization, and safe host routing when direct traffic resumes.
A production acceptance record
- Every hostname, origin, route class, data owner, DNS owner, and rollback owner is recorded.
- Hashed assets, public HTML, public APIs, private responses, uploads, errors, redirects, and ranges have explicit cache decisions.
- The cache key includes every value that changes a representation and excludes only proven noise.
- The origin validates the CDN or uses private connectivity; direct bypass is tested and controlled.
- TLS, IPv4, IPv6, HTTP versions, redirects, HSTS, CORS, cookies, compression, and security headers match the intended behavior.
- Hits, misses, bypasses, revalidation, stale service, purge, shield, and request collapsing have been exercised.
- Tests cover approved regions and customer ISPs, including India where it matters to the audience.
- Logs, trace IDs, metrics, alerts, costs, retention, and privacy controls are available to the operators who will handle an incident.
- Versioned releases, narrow invalidation, origin-capacity protection, DNS rollback, and configuration rollback have passed.
Primary references
- RFC 9111: HTTP Caching
- Amazon CloudFront cache policies and cache keys
- Amazon CloudFront origin-request policies
- Cloudflare cache keys
- Cloudflare tiered caching
- Google Cloud CDN caching and cache keys
- Google Cloud CDN private-content controls
- Azure Front Door caching
- Azure Front Door purge behavior
- Fastly caching concepts
- Fastly shielding
- DE-CIX India connected networks


