How to Test CDN Latency: Tools, Methods and Benchmarks

This playbook produces a defensible CDN latency test in about 45 minutes of hands-on work: connection phases split into DNS, TCP, TLS and time-to-first-byte, measured from five regions, segmented by cache hit and cache miss, and reported as p50/p95/p99 instead of an average. Budget roughly 1,000 samples per region per segment if you want a p99 you can defend in a design review. In 2026 measurements, within-region cache hits typically land at 15–45 ms TTFB and cache misses at 80–300 ms. A single blended number sitting between those two ranges tells you nothing useful.

Engineer running a CDN latency test from multiple regions with percentile charts for TTFB and throughput

How to test CDN latency: five variables you must hold still

Most latency numbers people quote are unreproducible because two runs differed in something nobody wrote down. Fix these five axes before the first request goes out, and record them next to every result:

  • Cache state — hit, miss, and revalidation are three different systems. Never average them together.
  • Object size — a 256 KB asset measures round trips; a 100 MB asset measures congestion window growth and edge disk. Use both, separately.
  • Vantage point — cloud VMs sit in carrier-dense facilities and will read 10–30 ms more optimistic than residential or mobile eyeballs.
  • Protocol — HTTP/2 over TCP and HTTP/3 per RFC 9000 have different handshake costs. Pin one per run.
  • Connection reuse — a warm connection hides DNS, TCP and TLS entirely. Decide whether you are measuring first-view or steady-state.

Prerequisites for a repeatable CDN latency test

  • Five small VMs (2 vCPU is plenty) in distinct regions: North America east and west, Western Europe, Southeast Asia, South America.
  • k6 installed on each, plus dig and mtr for path checks.
  • A test path on your CDN zone that you control end to end, with a known cache policy.
  • Two static objects staged at origin: 256 KB and 100 MB, both with deterministic content.
  • Roughly 3 GB of egress budget per region per full run (about 15 GB for five regions).

Step 1: instrument the origin so misses are attributable

Without origin-side timing you cannot tell whether a slow miss is edge-to-origin network time or your own application. Add a log format that captures upstream time and cache status, and emit Timing-Allow-Origin so browser Resource Timing later gives you the same phase breakdown for real users.

log_format cdnbench '$remote_addr $host "$request" '
                    'status=$status bytes=$body_bytes_sent '
                    'req_time=$request_time upstream_time=$upstream_response_time '
                    'proto=$server_protocol tls=$ssl_protocol '
                    'cache=$upstream_cache_status';

server {
    listen 443 ssl;
    http2 on;
    server_name ORIGIN_HOST;
    access_log /var/log/nginx/cdnbench.log cdnbench;

    location /bench/ {
        add_header Cache-Control "public, max-age=300" always;
        add_header Timing-Allow-Origin "*" always;
        add_header X-Origin-Node ORIGIN_HOST always;
    }
}

Replace ORIGIN_HOST with your origin hostname. Keep max-age short enough that you can force fresh misses without purging.

Step 2: confirm the network path before you blame the CDN

Resolve the hostname from each vantage point and check which address family and which route you actually get. A "slow region" is frequently a DNS answer pointing somewhere unexpected, or an IPv6 path with a different transit mix.

dig +noall +answer TEST_HOST A
dig +noall +answer TEST_HOST AAAA
mtr --report --report-cycles 100 --tcp --port 443 TEST_HOST

Substitute TEST_HOST with your CDN hostname. Record the resolved address per region in your results file; if it changes mid-run, your samples are not comparable.

Step 3: run the latency benchmark with cache state as a scenario

Two k6 scenarios, one hitting a cacheable path and one generating guaranteed misses through unique path segments. Path-based cache busting avoids query strings, which some configurations strip or ignore.

import http from 'k6/http';
import { check } from 'k6';

export const options = {
  scenarios: {
    hit: {
      executor: 'constant-arrival-rate',
      rate: 4, timeUnit: '1s', duration: '10m',
      preAllocatedVUs: 20, exec: 'cacheHit'
    },
    miss: {
      executor: 'constant-arrival-rate',
      rate: 2, timeUnit: '1s', duration: '10m',
      preAllocatedVUs: 20, exec: 'cacheMiss'
    }
  },
  discardResponseBodies: false
};

const BASE = 'https://TEST_HOST';

export function cacheHit() {
  const res = http.get(BASE + '/bench/static/asset-256k.bin', { tags: { seg: 'hit' } });
  check(res, { 'served': function (r) { return r.status === 200; } });
}

export function cacheMiss() {
  const unique = Date.now().toString() + '-' + __VU.toString();
  const res = http.get(BASE + '/bench/uncached/' + unique + '/asset-256k.bin', { tags: { seg: 'miss' } });
  check(res, { 'served': function (r) { return r.status === 200; } });
}

Replace TEST_HOST with your CDN hostname. That run yields 2,400 hit samples and 1,200 miss samples per region. Read four metrics from the summary: http_req_connecting (TCP), http_req_tls_handshaking (TLS), http_req_waiting (TTFB, the number most people mean by latency), and http_req_receiving (transfer).

For throughput, add a third scenario using the per-VU-iterations executor with one virtual user and 20 iterations against the 100 MB object, then compute megabits per second from body size divided by http_req_receiving. Run it after the latency scenarios, never concurrently — a saturated uplink corrupts both.

Step 4: size the sample set for the percentile you want to quote

Use n = k / (1 − p), where k is the number of samples you want above the percentile. With k = 10, p95 needs 200 samples and p99 needs 1,000 — enough to report, not enough to be precise. With k = 100, p95 needs 2,000 and p99 needs 10,000. Anything below k = 10 is a single unlucky TCP retransmit away from a fictional result.

Validation: what a good CDN latency test looks like in 2026

Compare your output against these reference ranges, gathered from public 2025–2026 measurements across cloud vantage points. They are ranges, not targets, and they assume HTTP/2 with connection reuse.

Segmentp50 TTFBp95 TTFBInvestigate if
Cache hit, same continent15–45 ms40–90 msp95 exceeds 3x p50
Cache hit, cross-continent25–70 ms60–150 msp50 above 100 ms
Cache miss to origin80–300 ms200–600 msupstream_time is most of it
TLS 1.3 handshake, cold1 RTT + 5–15 ms2 RTTconsistently above 2 RTT
Single-connection throughput, 100 MB150–600 Mbpsn/aflat regardless of distance

The single most useful signal in this table is the ratio between p95 and p50 on cache hits: anything above 3x points at edge contention or an unstable path, not at distance.

How to read a CDN latency test honestly

A cache-hit test and a cache-miss test measure two different systems. In 2026 measurements, an edge cache hit typically returns first byte in 15–45 ms within-region while a miss that traverses to origin adds 80–300 ms, so blending the two into one average commonly understates p95 by a factor of 2 to 5. Segment on the Cache-Status response header defined in RFC 9211 before you compute any percentile.

Three more corrections that separate a real latency benchmark from a screenshot:

  • Synthetic is a floor, not a forecast. Cloud vantage points overstate real-world performance. Pair this run with Resource Timing data from real browsers before making a vendor decision.
  • Cold caches inflate the first 60 seconds. Discard the first minute of every scenario, or your p99 is a warm-up artifact.
  • Throughput and latency trade against each other. Larger initial congestion windows improve small-object TTFB and can hurt loss recovery on lossy mobile paths. Measure both, then decide which one your workload cares about.

Failure modes: symptom, cause, fix

SymptomLikely causeFix
Miss scenario shows hit latencyPath normalization collapsing unique segmentsVerify Cache-Status per response; move the unique token into the filename
p99 spikes every run at the same clock minuteCron or log rotation on the load generatorMove the VM off shared schedulers; re-run and compare
TLS time near zero for all samplesConnection reuse across iterationsDisable reuse for a first-view run, keep it on for steady-state
One region 200 ms worse, path looks fineDNS steering to a distant answerCompare resolved addresses across regions and resolvers
Throughput caps at exactly 1 GbpsLoad generator NIC, not the CDNCheck instance network tier before recording the result

Four of these five failures live on your side of the wire, which is why every anomalous result deserves a path and DNS check before a support ticket.

Tear-down

Stop the arrival-rate scenarios first — an abandoned 10-minute run left in a loop is the most common surprise egress line item after a benchmark week. Purge the /bench/uncached/ prefix so junk objects do not occupy edge storage, remove the extra log_format if your log pipeline parses strictly, and archive the raw k6 JSON summaries with the resolved addresses and timestamps. Without those, next quarter's run is not a comparison.

Tuning: turn one run into a continuous latency benchmark

Once the numbers stabilize, cut the duration to 3 minutes, drop the miss rate to 1 request per second, and schedule it hourly from two regions with a threshold on http_req_waiting p95. That is roughly 400 MB of egress per region per day and gives you a trend line instead of an anecdote.

When you re-run against a candidate provider, keep the object set, region list and protocol identical. Providers with fast onboarding make same-week A/B testing realistic — BlazingCDN, for example, provisions in about an hour and serves from NVMe SSD edge storage, which mainly shows up in your data as flatter p95 on large-object hits. If you are comparing designs rather than vendors, the reference material on custom enterprise CDN infrastructure covers the origin shield and storage variables that move miss latency.

FAQ: CDN latency testing methods and benchmarks

How do I test CDN latency from multiple regions?

Run the same load script from small VMs in at least five geographically separated regions, resolving the CDN hostname locally in each one. Identical object sets, identical protocol, identical duration. Record the resolved IP address per region, because DNS steering differences explain more cross-region variance than physical distance does.

Does ping measure CDN latency?

No. ICMP round-trip time measures network path latency only and ignores TLS handshake, cache lookup, origin fetch and transfer. Ping is useful as a lower bound and for detecting routing changes, but a CDN latency test must measure time-to-first-byte over HTTPS, segmented by cache status, to reflect what users experience.

What is a good TTFB for a CDN in 2026?

For a cache hit measured from a cloud vantage point on the same continent, 15–45 ms at p50 and under 90 ms at p95 is typical in 2026. Cross-continent hits run 25–70 ms at p50. Real-user numbers from residential and mobile networks usually add 10–30 ms on top of these figures.

How many samples are needed for a reliable p99 latency benchmark?

Roughly 1,000 samples per segment per region for a reportable p99, and about 10,000 for a tight confidence interval. The rule is n = k / (1 − p), where k is the count of samples above the percentile; k below 10 makes the result hostage to a single retransmit or garbage collection pause.

Should a CDN latency test use HTTP/2 or HTTP/3?

Test both, in separate runs, and never mix them in one percentile. HTTP/3 per RFC 9000 typically wins on lossy or high-RTT paths by removing head-of-line blocking and saving a round trip on resumption, while HTTP/2 over TCP often matches or beats it on clean, low-latency wired paths.

Run it this week and publish the ratio

Take one afternoon, stage the two objects, and run the hit and miss scenarios from three regions. Then post one number to your team channel: the p95-to-p50 ratio on cache hits, per region. If it is under 2.5, your edge tier is healthy and any user complaints are coming from origin or client code. If it is above 3, you have edge variance worth chasing before anyone opens a vendor conversation. Either way you now have a baseline that a future migration or config change can be measured against.