A video player seeking to the middle of a movie asks for a few megabytes. Under our old range caching, that one request could make Cloudflare pull 128 MiB from the origin and move it between data centers, sometimes after the viewer had already left. Range requests are how large files get delivered, and caching them naively easily leads to wasted network bandwidth and wasted disk operations when running a CDN at scale.
So we rebuilt how our cache handles them. For R2, Cloudflare’s object storage, p99 time to first byte dropped by about half, and a follow-up improvement cut tiered cache traffic for R2 and similar workloads by about 6%.
Today, we’re making that new implementation available as Origin Range Requests, a self-service feature on all plans. There’s no range size to tune and no need to contact support. Turn it on with a Cache Rule, and Cloudflare handles the rest.
A request for part of a file
An HTTP range request asks for part of a resource instead of the whole resource. The client sends a normal GET with a Range header:
GET /movie.mp4 HTTP/1.1
Host: example.com
Range: bytes=1048576-2097151If the server supports the request, it returns 206 Partial Content and identifies the returned bytes with Content-Range:
HTTP/1.1 206 Partial Content
Content-Range: bytes 1048576-2097151/524288000
Content-Length: 1048576Range requests are common for large objects. A video player can request bytes around a seek position instead of the whole file. A download manager can resume after an interruption. A package manager can fetch part of an artifact. A client can inspect the end of a file without downloading everything before it.
The client decides whether to request a range, and Cloudflare does not change that request. Instead, Origin Range Requests changes how Cloudflare satisfies it.
The range-size problem
Cloudflare has supported range-request caching for years via static zone configuration.
The previous implementation resembled the cache-slicing described in an nginx blog post: each client request was mapped to fixed-size ranges of the file, with the range size and file extensions configured per zone.
The size of each range created a tradeoff: small ranges led to many small origin requests and memory pressure on Cloudflare from coalescing all of the requests in-memory, while large ranges caused Cloudflare to download much more data than the client requested. Updates to the range size itself would invalidate cache entries, since the size was part of the cache key.
This design also meant people requesting content through our network could have unpleasant experiences accessing these files. Concurrent requests that fell inside the same range, no matter how far apart each client’s requested range was, could block each other. Our nginx-based cache also couldn’t combine range caching with concurrent streaming, a feature we built to let multiple clients read a file from cache while it’s still being written. Turning both on together cut responses short, so each range had to be fully downloaded before anyone could read it, and popular files needed a separate origin fetch for every range.
The extra bytes could also cross several parts of our distributed cache: from the origin to one data center, between cache tiers, and then toward the client. Every hop multiplied the waste. No value works equally well for a small seek, a sequential download, a large media file, and every origin and cache topology.
Our internal cache limitations were directly exposed to customers, who should not need to understand our capabilities in-depth to have files served efficiently.
Fortunately, our transition from nginx to a Pingora-based caching server created an opportunity to address the implementation problems that had forced a hard-to-use setting onto customers.
Fixed-size cache chunks
The fix came from something Pingora was already doing. To make it easy to resume interrupted downloads, it stores every cached file on disk in fixed-size chunks. If the cache already thinks in chunks, the range size doesn’t need to be a setting at all, it can just be the chunk!
For a range request, Cloudflare checks which requested bytes are already cached. It serves those bytes directly. For missing bytes, it fetches origin ranges aligned to the cache’s fixed-sized-chunk boundaries, stores the result in the same chunks used for ordinary cache storage, and returns only the bytes requested by the client.
For example, a request for bytes 150-250 can cause Cloudflare to fetch the aligned chunk containing those bytes. The downstream response still contains only 150-250.
This does not eliminate extra work for requests smaller than our chunk size — such a request can still require fetching the whole chunk containing it. We also are taking on the cost of more work for non-aligned ranges. The difference is that the extra work is bounded and predictable, and no longer determined by a customer-selected range size that can cause unpredictable effects on us and origins.
Coordinating cached and in-flight data
Parsing the Range header is straightforward. The harder problem is producing one correct response when the requested bytes are split across cached data, an origin response, and downloads already in progress.
At a high level, the edge:
- Looks up the asset headers and range-coverage metadata.
- Reads whatever part of the asset it can from disk or streaming in memory.
- Fetches missing coverage from the origin using aligned ranges.
- Writes the new bytes and records the completed range in metadata.
- Combines the pieces and sets
Content-RangeandContent-Lengthfor the client response.

This is a coordinator, not a single cache lookup. One request can use a cached prefix, an in-flight download, and a newly fetched suffix. The internal origin response can be larger than the downstream response, but the downstream status and headers describe only the requested range.
If an existing download covers another request’s bytes, the second request can attach to that download instead of starting another origin fetch. This shares work during bursts for popular large files.
The design uses the existing cache data model. Headers identify the asset version, the body is stored in bounded chunks, and metadata records which ranges were written successfully.
Validate before composing
Composed range responses are valid only when all bytes belong to the same representation. The coordinator checks that each origin response matches the requested range, that the total asset size is stable, and that the entity tag remains consistent across subrequests.
The If-Range header lets a client request a range only if the representation still matches a known validator. If it does not match, Cloudflare returns the full representation instead of combining bytes from different versions.
Cloudflare also avoids composing ranges for a representation it is about to decompress because the range over a set of compressed bytes would be different from those selected by the range in a decompressed response. In these cases, Cloudflare serves the transformed full response rather than advertising a range it cannot honor correctly.
The resulting behavior is standard HTTP:
- A satisfiable range receives
206 Partial Contentwith an accurateContent-Range. - An unsatisfiable range receives
416 Range Not Satisfiable. - A response that cannot safely be ranged is served as the complete representation.
What changed in production
Before opening Origin Range Requests to everyone, we moved existing users of the old range-caching feature onto the new implementation, starting with the workloads that hurt us most.
Those workloads had a lot of concurrent range requests against very large files, often on very spiky traffic. Configured range sizes on the old system ran from tens of kilobytes to tens of gigabytes. With a large range size, a request that wouldn’t fill a floppy disk could make us download a few DVDs’ worth of data from the origin and move it across several data centers, sometimes after the client had already given up and gone elsewhere. With a small range size, one request turned into a long line of tiny origin requests, each holding memory while it waited. Both patterns caused real operational incidents for us.
R2 was the largest internal user. Its configured range size was 64 MiB, so a small read that crossed a range boundary could cost us 128 MiB of transfer between data centers. After moving R2 to the new implementation, we saw:
- Faster responses. Across R2’s storage regions, p99 time to first byte dropped by roughly half on both cache hits and misses.
- Fewer bytes on our network. Our most recent change taught the coordinator to combine bytes already on disk with bytes fetched from upstream in a single response. Before, a request was served either entirely from cache or entirely from upstream. After we turned this on for R2, tiered cache traffic for R2 and similar workloads dropped about 6% week over week, which is petabytes less data moving between our data centers every day.
Getting started
Once the cache no longer depended on a customer-selected range size, the product configuration could not be simpler. Customers select matching traffic through Cache Rules. Cloudflare handles the chunking and cache coordination.
Origin Range Requests is intended for traffic where clients naturally request parts of a large cacheable object:
- Progressive and seekable video such as MP4
- Resumable large downloads
- Software distributions and package repositories
- Disk images, archives, and other multi-gigabyte objects
- AI training and inference workloads that transfer large model files or fetch selected regions of model artifacts
- Clients that fetch metadata or a small region of a larger file
We intend to make this the default way certain file types are served on our network, modifiable by cache rules.
This feature does not change normal cache eligibility. Enabling it does not make an otherwise uncacheable response cacheable. The origin also needs to support byte ranges for partial origin fetches to be useful.
To turn it on, create a Cache Rule that matches your large-file traffic and set Origin Range Requests to On. You can do this in the dashboard under Caching > Cache Rules, or through the API with origin_range_requests: { "mode": "on" }. Customers choose the traffic; the edge handles the cache coordination and correctness requirements.

