HTTP 416 Range Not Satisfiable

Range Not Satisfiable

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 -c reads 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-Range with 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 ignore Range on an empty representation. Go’s http.ServeContent does, 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.

Try it in the HTTP Status Codes

416 Range Not Satisfiable

  1. 416Range Not Satisfiable4xx Client error

    The range the client asked for lies outside the resource — a seek past the end of a file, most often. Content-Range: bytes */<length> says how big the resource actually is.

    When to use it

    Range requests on a file whose size changed since the client last looked.

    Common mistakes

    Sending it for a range that only runs past the end — bytes=0-99999 of a 1,000-byte file is satisfiable, and gets bytes 0-999 in a 206 — and counting on it in a client: a server may ignore Range and send the whole file as a 200.

    Not cacheable by defaultRFC 9110Formerly Requested Range Not Satisfiable

Every code, by class

Codes marked unofficial come from one server’s own convention — nginx, Cloudflare, cPanel, Microsoft — and are not in the IANA registry.

Runs in your browser. Nothing you enter is sent anywhere.