Debugging 206 Partial Content Cache Behaviour
Problem Statement
Video playback is generating far more origin traffic than the catalogue size suggests it should, and the CDN dashboard reports a healthy hit ratio. Ranged requests behave differently enough from ordinary ones that both observations can be true simultaneously. This procedure establishes what is actually happening.
Prerequisite Concepts
- Range requests and partial content caching —
Accept-Ranges,Content-Range, and the conditions under which a206may be stored. - Strong vs weak ETags explained — why a weak validator silently disables resumption.
- Interpreting X-Cache and CF-Cache-Status headers — reading the status token you will be comparing.
Step-by-Step Resolution
Step 1 — Confirm range support end to end
curl -sI https://example.com/media/lecture-04.mp4 \
| grep -iE 'accept-ranges|^etag|content-length|cache-control'
Accept-Ranges: bytes should be present, together with a quoted, non-weak ETag. A weak validator here is worth fixing before going further, because it disables If-Range and therefore every resumed download.
Then confirm a range is actually honoured:
curl -s -o /dev/null -D - -H 'Range: bytes=0-1023' \
https://example.com/media/lecture-04.mp4 | head -6
HTTP/2 206 with Content-Range: bytes 0-1023/524288000 is correct. A 200 means the range was ignored or stripped somewhere in the path.
Step 2 — Test whether the range is stored
Issue the identical range twice and compare:
for i in 1 2; do
curl -sI -H 'Range: bytes=1048576-2097151' https://example.com/media/lecture-04.mp4 \
| grep -iE '^age|cf-cache-status|x-cache'
done
A second response with a non-zero Age means the fragment or its containing block is stored. A permanent MISS with Age: 0 means every range is being forwarded to the origin — which is the finding that explains an origin bill that does not match the dashboard.
Step 3 — Isolate the tier
Compare the origin’s own behaviour against the edge’s:
# Direct to the origin
curl -sI -H 'Range: bytes=0-1023' \
--resolve example.com:443:203.0.113.10 https://example.com/media/lecture-04.mp4 | head -3
# Through the CDN
curl -sI -H 'Range: bytes=0-1023' https://example.com/media/lecture-04.mp4 | head -3
If the origin answers 206 and the edge answers 200, the edge is not forwarding the range. If the origin answers 200, range support is missing at the source and no CDN feature can compensate — slicing depends on the origin honouring the block requests the edge issues.
Step 4 — Establish the block granularity
A cache that stores aligned blocks behaves differently from one that stores exact ranges. Request a range offset by one byte from a previously requested one:
curl -sI -H 'Range: bytes=1048576-2097151' https://example.com/media/lecture-04.mp4 | grep -i '^age'
curl -sI -H 'Range: bytes=1048577-2097152' https://example.com/media/lecture-04.mp4 | grep -i '^age'
Two hits mean the cache is serving both from the same stored block — aligned storage, which is what you want. A hit followed by a miss means it is storing exact ranges, and a player that never repeats an exact byte window will never hit.
Step 5 — Check how the metrics are counting
Content-Length on a 206 describes the fragment. An egress calculation that sums it under-reports ranged traffic by whatever fraction the ranges represent:
# Correct: derive the resource size from Content-Range, the transfer from Content-Length
curl -sI -H 'Range: bytes=0-1023' https://example.com/media/lecture-04.mp4 \
| awk -F'[ /]' '/[Cc]ontent-[Rr]ange/ {print "resource bytes: " $NF}'
Expected Output / Verification
A correctly configured path produces 206 at every tier, a non-zero Age on a repeated range, hits on offset ranges within the same block, and an origin byte count that tracks the number of distinct blocks watched rather than the number of viewers.
HTTP/2 206
Content-Range: bytes 1048576-2097151/524288000
Content-Length: 1048576
Age: 3241
CF-Cache-Status: HIT
Edge Cases
- A weak
ETagon a rangeable resource.If-Rangerequires strong comparison, so resumption always restarts. The symptom is enormous egress with no obvious configuration fault. - Multi-range requests.
multipart/byterangesis inconsistently handled by intermediaries and rarely cached. Limiting or rejecting multi-range requests is usually better than relying on them. - Compression between two range requests. Offsets refer to transferred bytes, so a re-encoding invalidates every offset the client holds mid-download.
- Mismatched block sizes between tiers. An edge slicing at one megabyte in front of a shield slicing at eight will miss at the shield on nearly every request.
- A
416after a deploy. If the resource shrank, previously valid offsets are now out of range. Clients holding partial copies see416until they restart the download. - Range support disabled by a security rule. Some WAF configurations strip or reject
Rangeheaders to mitigate specific attacks, which silently converts every ranged request into a full transfer.
Frequently Asked Questions
Why does a range request return 200 instead of 206?
Either the server does not support ranges for that resource, or an intermediary stripped the Range header, or an If-Range precondition failed. The third case is the most common and is a correct, designed fallback rather than an error.
Is a permanent MISS on ranged requests always a bug?
No. Several proxies deliberately decline to store partial content because combining fragments correctly is difficult. It is a bug only if you expected the tier to cache ranges, which usually means a slicing or range-caching feature that has not been enabled.
What does Content-Length mean on a 206?
The size of the fragment being sent, not of the resource. The resource size appears after the slash in Content-Range. Metrics that sum Content-Length will therefore under-report ranged egress substantially.
Can two caches disagree about block boundaries?
Yes, and it is a common cause of poor shield hit ratios on media. If the edge slices at one megabyte and the shield at eight, the edge’s requests never align with what the shield stores. Configure the same block size at every tier.
Related
- Range requests and partial content caching covers the underlying semantics this procedure tests.
- Why large objects miss more often at the edge explains the size-related mechanisms that look similar from the outside.
- How to debug CDN cache key mismatches applies the same isolation method to the key rather than the range.