Origin Range Requests: Serving Large Files Without Tuning Range Sizes
Cloudflare's new Origin Range Requests replaces per-zone range sizes with fixed-size chunks, cutting R2 p99 time to first byte roughly in half.

A video player seeking to the middle of a movie asks for a few megabytes. Under Cloudflare's previous range caching, that single request could pull 128 MiB from the origin and move it between data centers, sometimes after the viewer had already left. That gap between what a client asks for and what the network moves is the core problem here, and it gets worse the larger the files get.
Cloudflare has rebuilt how its cache handles range requests and is now offering the result as Origin Range Requests, a self-service setting on all plans. For R2, its object storage product, p99 time to first byte dropped by about half. A follow-up change then cut tiered cache traffic for R2 and similar workloads by about 6%. There's no range size to pick and no support ticket to file. You create a Cache Rule, and the edge handles the rest.
How range requests work
An HTTP range request asks for part of a resource rather than the whole thing. 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 it, the response is 206 Partial Content, with Content-Range identifying exactly which bytes came back:
HTTP/1.1 206 Partial Content
Content-Range: bytes 1048576-2097151/524288000
Content-Length: 1048576Video players use this to fetch bytes around a seek position. Download managers resume interrupted transfers with it. Package managers pull a single artifact from a larger bundle, and some clients read just the end of a file to find metadata before anything else. The client decides when to ask for a range. Cloudflare doesn't change that request. What Origin Range Requests changes is how the cache satisfies it.
Why the old design struggled
Cloudflare had supported range caching for years through static zone configuration. The old approach resembled the cache-slicing model described in an nginx blog post. Each client request was mapped onto fixed-size ranges of the file, with the range size and file extensions set per zone.
That range size forced a tradeoff with no good setting. Small ranges meant many small origin requests and memory pressure from holding all those pending requests in memory. Large ranges meant pulling far more data than any client asked for. Changing the range size also invalidated existing cache entries, because the size was baked into the cache key.
Behavior got worse under concurrency. Requests landing in the same range could block each other, even when the clients wanted bytes that were far apart. The nginx-based cache also couldn't combine range caching with concurrent streaming, which lets many clients read a file while it's still being written into cache. Turning both on together cut responses short, so each range had to finish downloading before anyone could read it. Popular files ended up needing a separate origin fetch for every range.
Every extra byte also multiplied as it moved. A surplus fetched from the origin crossed into one data center, then between cache tiers, then toward the client. Each hop repeated the waste. And no single range size suited every case at once: a small seek, a sequential download, a large media file, and every origin and cache topology each want something different. Customers shouldn't need to understand Cloudflare's internal cache limits to get files served efficiently, but the old setting pushed exactly that burden onto them.
Fixed-size chunks instead of a configured range
The fix came from the cache server itself. Cloudflare moved from nginx to a Pingora-based caching server, and Pingora already stores every cached file on disk in fixed-size chunks. That design makes interrupted downloads easy to resume. Once the cache thinks in chunks, the range size stops being a customer setting. The chunk becomes the range.
For a range request, the edge first checks which requested bytes are already cached and serves those directly. For missing bytes, it fetches origin ranges aligned to chunk boundaries, writes the result into the same chunks used for ordinary caching, and returns only the bytes the client asked for. A request for bytes 150 through 250 might trigger a fetch of the whole aligned chunk containing them, but the downstream response still contains only 150 through 250.
This doesn't remove extra work. Requests smaller than a chunk can still pull the whole chunk, and non-aligned ranges carry some added cost. What changes is that the overhead is now bounded and predictable. It no longer depends on a range size a customer picked months earlier, which could behave unpredictably for both Cloudflare and the origin.
Combining cached and in-flight bytes
Parsing the Range header is the easy part. The hard part is producing one correct response when the requested bytes are split across cached data, a fresh origin response, and a download already underway.
At a high level, the edge looks up the asset headers and range-coverage metadata, reads whatever it can from disk or from an in-memory stream, fetches the missing coverage from the origin using aligned ranges, writes the new bytes and records the completed range in metadata, then assembles the pieces and sets Content-Range and Content-Length for the client.
This works more like a coordinator than a cache lookup. A single request might draw a cached prefix, an in-flight download, and a newly fetched suffix. The internal origin response can be larger than what goes downstream, but the status and headers describe only the requested range. If another request's bytes are already being downloaded, the second request attaches to that transfer instead of opening a new origin fetch. During bursts on a popular large file, that shares the work instead of multiplying it.
The design reuses the existing cache data model. Headers identify the asset version, the body sits in bounded chunks, and metadata tracks which ranges were written successfully. Nothing new had to be invented for storage.
Validation before composition
A composed response is only valid if every byte comes from the same representation of the resource. The coordinator checks that each origin response matches the range it was asked for, that the total asset size holds steady, and that the entity tag stays consistent across subrequests.
The If-Range header lets a client request a range only if the representation still matches a validator it already has. When it doesn't match, Cloudflare returns the full representation rather than stitching together bytes from two versions of a file. That's the case that quietly corrupts media files if it's handled wrong.
Compression adds a wrinkle. A byte range over compressed data doesn't correspond to the same bytes after decompression, so Cloudflare skips composing ranges for content it's about to decompress. It serves the transformed full response instead of promising a range it can't honor correctly. The resulting behavior follows standard HTTP. A satisfiable range gets 206 Partial Content with an accurate Content-Range. An unsatisfiable one gets 416 Range Not Satisfiable. Anything that can't safely be ranged gets the complete representation.
What changed in production
Before opening the feature to everyone, Cloudflare moved existing users of the old range caching onto the new implementation, beginning with the workloads that caused the most trouble. Those workloads had heavy concurrent range traffic against very large files, often with sharp traffic spikes. Configured range sizes on the old system ran from tens of kilobytes to tens of gigabytes.
At the large end, a request that wouldn't fill a floppy disk could make Cloudflare download a few DVDs' worth of data from the origin and haul it across several data centers, sometimes after the client had already given up. At the small end, one request fanned out into a long line of tiny origin requests, each tying up memory while it waited. Both patterns caused real operational incidents.
R2 was the largest internal user, with a configured range size of 64 MiB. A small read that crossed a range boundary could therefore cost 128 MiB of transfer between data centers. After the move, two things stood out:
- Faster responses. Across R2's storage regions, p99 time to first byte fell by roughly half on both cache hits and cache misses.
- Fewer bytes on the internal network. The most recent change taught the coordinator to combine bytes already on disk with bytes fetched from upstream in a single response. Previously, a request was served entirely from cache or entirely from upstream. With that turned on for R2, tiered cache traffic for R2 and similar workloads dropped about 6% week over week. Cloudflare describes that as petabytes less data moving between its data centers each day.
Where it fits and how to turn it on
Origin Range Requests suits traffic where clients naturally pull pieces of a large cacheable object. That covers progressive and seekable video such as MP4, resumable large downloads, software distributions and package repositories, disk images and archives, and multi-gigabyte objects in general. It also covers AI training and inference workloads that move large model files or read selected regions of model artifacts, along with clients that fetch metadata or a small slice of a bigger file.
Cloudflare says it intends to make this the default way certain file types are served on its network, with cache rules able to override that. For teams planning around it, a few limits matter. The feature doesn't change normal cache eligibility, so enabling it won't make an uncacheable response cacheable. And the origin still has to support byte ranges, or partial origin fetches won't gain anything.
Setup is a single rule. Create a Cache Rule that matches your large-file traffic and set Origin Range Requests to On. That's available under Caching > Cache Rules in the dashboard, or through the API with origin_range_requests: { "mode": "on" }. The trade here is clear: you choose which traffic qualifies, and the edge takes care of chunking, coordination and correctness. My read is that the R2 numbers are the ones to watch. A 6% cut in tiered cache traffic won't show up on a single seek, but across petabytes of daily movement, it's the kind of change that shows up on the bill and in tail latency.