HTTP 206 is Partial Content: the server is answering a Range request with one or more parts of the representation instead of all of it (RFC 9110 section 15.3.7). A single part comes with a Content-Range header saying which bytes these are and how long the whole thing is; Content-Length counts only the bytes in this message.
The exchange
GET /videos/intro.mp4 HTTP/1.1
Range: bytes=0-1023
HTTP/1.1 206 Partial Content
Accept-Ranges: bytes
Content-Range: bytes 0-1023/146515
Content-Length: 1024
Content-Type: video/mp4
Byte positions start at zero and are inclusive (RFC 9110 section 14.1.2), so 0-1023 is 1,024 bytes. bytes=500- asks for everything from offset 500, bytes=-500 for the last 500 bytes, and a last position past the end is trimmed to the end rather than refused.
Who asks for ranges
- Video and audio elements. Browsers fetch media in ranges and seek by asking for new ones. Apple requires servers that host media for Safari on iOS to support byte-range requests.
- Resumed downloads. Download managers,
curl -C -andwget -cask for the bytes they’re missing.
Resuming safely with If-Range
GET /files/backup.tar HTTP/1.1
Range: bytes=1048576-
If-Range: "v42"
If the ETag still matches, the server sends the rest as a 206. If the file changed, it ignores Range and sends the whole new file as a 200 (RFC 9110 section 13.2.2). Without it, a client can splice the end of one version onto the start of another; RFC 9110 allows combining parts only when they share the same strong validator.
Multiple ranges
Range: bytes=0-99,200-299 gets one 206 with Content-Type: multipart/byteranges; boundary=... and a Content-Range inside each part, never in the top-level headers (section 15.3.7.2). A server may merge overlapping ranges, and may ignore or reject a list of many overlapping or out-of-order ranges as a likely denial-of-service attempt; nginx’s max_ranges caps the count.
Caching
A 206 is heuristically cacheable. RFC 9111 lets a cache store partial responses and combine them into a complete one when they share a strong validator.
Gotchas
- Range support is optional: a server may ignore
Rangeand send a 200 with everything, so check the status before treating the body as a slice. Accept-Ranges: bytesis advice, not a promise;Accept-Ranges: noneasks clients not to try.- Ranges count bytes of the encoded content, so a gzip-compressed response is sliced after compression.
In your stack
nginx serves ranges for static files by default; max_ranges 0; turns them off. Express’s express.static and res.sendFile handle ranges (acceptRanges: false turns them off), as does Go’s http.ServeContent.
curl -r 0-1023 -o part.bin -D - https://example.com/videos/intro.mp4 # one range; headers on stdout
curl -C - -O https://example.com/files/backup.tar # resume where the file ends
Search 206 416 in the tool below to compare it with Range Not Satisfiable.