HTTP Range Requests and Partial Content, Explained
HTTP range requests let a client ask for just part of a resource, enabling video seeking, resumable downloads, and partial file fetches over HTTP.
An HTTP range request lets a client ask a server for a specific byte range of a resource instead of the whole thing, and the server replies with a 206 Partial Content response containing just that slice. It’s the mechanism behind seeking to the middle of a video without downloading everything before it, resuming an interrupted download from where it left off, and fetching only the header bytes of a large file to inspect its metadata.
How a range request works
The client sends a Range header specifying which bytes it wants:
GET /video.mp4 HTTP/1.1
Range: bytes=1000000-1999999
If the server supports range requests for that resource, it responds with 206 Partial Content instead of the usual 200 OK, along with headers describing exactly what was sent back:
HTTP/1.1 206 Partial Content
Content-Range: bytes 1000000-1999999/15728640
Content-Length: 1000000
Content-Range echoes back the byte range actually returned along with the resource’s total size, and Content-Length reflects the size of just this chunk — not the full resource. A server that doesn’t understand the Range header simply ignores it and returns the full resource with a normal 200 OK, which is why range requests are safe to send speculatively without breaking compatibility with servers that don’t support them.
How a server advertises support
A server signals that it accepts range requests via the Accept-Ranges header, sent on a normal response before any range request is even made:
Accept-Ranges: bytes
Clients — browsers, download managers, video players — check for this header to decide whether to enable features like seek bars or resumable downloads. A server that doesn’t support ranges either omits the header or sends Accept-Ranges: none.
Multiple ranges in one request
The Range header can request several disjoint byte ranges in a single request:
Range: bytes=0-499, 1000-1499
The server responds with multipart/byteranges, a multipart body where each part carries its own Content-Range header for that segment. This is less commonly used than single-range requests but shows up in tools that need scattered pieces of a large file — a PDF viewer fetching just the pages currently visible, for instance.
Conditional range requests
Range requests get combined with conditional headers to handle resources that might change mid-download. A resumable download sends If-Range alongside Range, referencing either an ETag or a last-modified timestamp:
Range: bytes=5000000-
If-Range: "33a64df551"
If the resource’s current ETag still matches, the server honors the partial range and resumes from byte 5,000,000. If it doesn’t match — the file changed since the download started — the server ignores Range entirely and sends the full resource back with 200 OK, so the client doesn’t stitch together bytes from two different versions of the file. This is the same ETag mechanism used for cache validation, reused here to guarantee range consistency instead.
Where range requests actually matter
Video and audio streaming. A <video> element’s seek bar relies entirely on range requests: dragging to the middle of a video triggers a new Range request for the bytes around that timestamp, rather than downloading from the start. This works even for plain progressive MP4 files served over ordinary HTTP, without any dedicated streaming protocol.
Resumable downloads. A download manager that supports pause-and-resume records how many bytes it already has and, on resume, sends a Range: bytes=<already-downloaded>- request for just the remainder, combined with If-Range to make sure the file hasn’t changed.
Parallel chunked downloads. Some download accelerators split a large file into several byte ranges and fetch them concurrently over multiple connections, then reassemble them locally — useful mainly on networks with per-connection throughput caps rather than overall bandwidth caps.
Partial reads of large files. Tools that only need part of a large object — reading just the footer of a large archive format to find an index, for example — can avoid downloading the entire file by requesting only the relevant byte range.
CDNs, caching, and range requests
Most CDNs handle range requests correctly out of the box, but caching them adds complexity: a CDN edge node caching partial responses has to track which byte ranges it already has cached for a given resource, and combine or split cached ranges as new requests come in for overlapping ranges. This is one reason large static video files are commonly served with range support directly through a CDN rather than a general-purpose origin server — CDNs are built to handle exactly this pattern efficiently, whereas a naive origin implementation might re-read the entire file from disk for every partial request regardless of range size.
Range requests and HTTP versions
Range requests work the same way conceptually across HTTP/1.1, HTTP/2, and HTTP/3 — the headers and status codes are identical. What differs is efficiency: HTTP/2 and HTTP/3 allow many range requests over a single connection without the per-request overhead HTTP/1.1 incurs from opening new connections, which matters for a video player issuing dozens of range requests as a user scrubs through a timeline. Standard HTTP status codes apply as usual alongside 206 — a range request for bytes beyond the resource’s actual size returns 416 Range Not Satisfiable rather than a truncated or empty body.
The takeaway
Range requests let a client fetch part of a resource instead of the whole thing, using a Range header on the request and a 206 Partial Content response with matching Content-Range and Content-Length headers on the reply. Pairing Range with If-Range keeps resumed downloads consistent even if the underlying file changes mid-transfer. It’s a quiet but foundational piece of the web platform: video seeking, resumable downloads, and partial file access all lean on the same handful of headers rather than any dedicated streaming protocol.
Keep reading
Takina · · 5 min read Gzip vs Brotli: HTTP Compression Compared
Gzip and Brotli both shrink HTTP responses before they hit the wire. How each algorithm works, and why Brotli usually compresses text tighter.
Takina · · 4 min read HTTP/2 vs HTTP/3: What Actually Changed
HTTP/2 fixed request multiplexing but stayed on TCP; HTTP/3 moves to QUIC over UDP to kill head-of-line blocking at the transport layer. The real differences.
Takina · · 5 min read HTTP Caching Headers: Cache-Control and ETag Explained
Cache-Control and ETag are the two headers that control HTTP caching — how long a response stays fresh and how to revalidate it cheaply, explained.