HTTP 416 is Range Not Satisfiable: none of the ranges in the request’s Range header can be served, or the client asked for so many small or overlapping ranges that the server rejected the set as a possible denial-of-service attack (RFC 9110 section 15.5.17). It was Requested Range Not Satisfiable until RFC 7233. For byte ranges the server should send Content-Range: bytes */<length> with the representation’s current size.
The exchange
For a file of 146,515 bytes:
GET /videos/intro.mp4 HTTP/1.1
Range: bytes=200000-
HTTP/1.1 416 Range Not Satisfiable
Content-Range: bytes */146515
What’s satisfiable
From RFC 9110 section 14.1.2, for a 1,000-byte file:
bytes=0-499 206 bytes 0-499/1000
bytes=900-1999 206 bytes 900-999/1000 a last position past the end is trimmed
bytes=-2000 206 bytes 0-999/1000 a suffix longer than the file means all of it
bytes=1000- 416 bytes */1000 the first byte is past the end
bytes=-0 416 a zero-length suffix asks for nothing
bytes=500-100 invalid the server may ignore it (200) or reject it
A range only has to start inside the file. Running past the end is not an error, and a server that answers it with 416 gets the rule wrong.
Where 416s come from
- Resuming a download that already finished. The client asks for
bytes=<size>-, one byte past the end.wget -creads that 416 as “The file is already fully retrieved; nothing to do.” - A file that shrank since the client stored its length. Resumes should send
If-Rangewith the ETag: a changed file then comes back whole as a 200, instead of as a 416 or a splice of two versions. - An empty file. The only satisfiable range on zero bytes is a non-zero suffix like
bytes=-500, though RFC 9110 lets a server ignoreRangeon an empty representation. Go’shttp.ServeContentdoes, and answers 200.
Don’t count on it
RFC 9110 notes that servers are free to ignore Range, and many answer with the whole representation as a 200 where a 416 would fit. A client has to handle 200, 206 and 416. On the server side, nginx and Go’s http.ServeContent answer 416 both to an unsatisfiable range and to one they can’t parse after bytes=, which RFC 9110 allows: a server may ignore or reject an invalid range.
416 isn’t heuristically cacheable.
curl -sS -o /dev/null -w '%{http_code}\n' -r 999999999- https://example.com/files/backup.tar # 416 if the file is smaller
http.ServeContent(w, r, "backup.tar", modTime, f) // 206, 416 with Content-Range: bytes */size, or 200
Search 206 416 in the tool below to compare the two.