# 416 Range Not Satisfiable: Causes and Fixes

> 416 Range Not Satisfiable means the requested byte range lies outside the resource. Find why resumed downloads and video requests fail, and how to fix it.

Source: https://howhttpworks.com/status-codes/416
Last reviewed: 2026-10-04

> **TL;DR:** 416 means the `Range` you sent starts beyond the end of the resource (or is otherwise invalid for its current size). Read `Content-Range: bytes */<size>` in the response, then finish, or throw away your partial data and re-fetch from byte 0.

## What it means

The client asked for part of a resource, and the server cannot give any of it. RFC 9110 §15.5.17 says the server should include a `Content-Range` with an unsatisfied-range value that reports the current length:

```http
GET /downloads/app-2.4.1.tar.gz HTTP/1.1
Host: example.com
Range: bytes=52428800-

HTTP/1.1 416 Range Not Satisfiable
Content-Range: bytes */31457280
Content-Type: text/html
```

The client has 50 MiB (52,428,800 bytes) and the file is now 30 MiB (31,457,280 bytes), so there is nothing past offset 52,428,800 to return.

What counts as unsatisfiable: the first byte position of every range is at or beyond the resource length, or a suffix range asks for zero bytes (`bytes=-0`), or the resource has zero length. A last-byte position that overshoots the end is fine and is clamped.

```text
Resource length 5000

Range: bytes=0-999999   -> 206, bytes 0-4999/5000   (clamped, fine)
Range: bytes=5000-      -> 416, bytes */5000        (first byte past the end)
Range: bytes=-100       -> 206, bytes 4900-4999/5000
```

Servers may also answer a malformed or abusive `Range` with 200 and the full body rather than 416. Syntactically broken ranges are supposed to be ignored.

## Who sent it?

416 comes from whichever layer evaluated the range against the object size. For static files that is usually the origin web server, but a CDN that has cached the object answers from its own copy. Check `Server`, `Via`, `Age` and `X-Cache` to find out which. Mismatched sizes between layers are the classic cause: the CDN holds the old 50 MiB file, the origin holds the new 30 MiB one, and a client resuming against one while the other answers gets 416.

## Common causes

1. **Resuming a complete download.** The client sends `Range: bytes=<size>-` for a file it already has. Servers answer 416, and tools react differently: wget says the file is already fully retrieved and exits cleanly, some scripts treat any non-2xx as a failure.
2. **The file changed.** A new build was uploaded under the same name, so the partial local copy is longer than the new file. This is the case `If-Range` is designed to catch; see [206](https://howhttpworks.com/status-codes/206).
3. **Inconsistent CDN or load-balanced origins** reporting different sizes for the same URL.
4. **Client bug:** computing the range with an off-by-one (`bytes=0-<size>` is fine, but `bytes=<size>-` is not), or using the compressed size where the server uses the uncompressed size or the reverse.
5. **Zero-byte object.** Any `Range` on an empty file is unsatisfiable. Some video players probing an empty placeholder file surface this as a 416.
6. **Truncated cache.** A browser cached a partial response for a video and later asked for a range using stale metadata. A hard reload clears it.

## Fix it

For clients, treat 416 as information, not just failure:

```javascript
async function resume(url, localBytes) {
  const res = await fetch(url, { headers: { Range: `bytes=${localBytes}-` } })

  if (res.status === 416) {
    const m = /bytes \*\/(\d+)/.exec(res.headers.get('Content-Range') ?? '')
    if (m && Number(m[1]) === localBytes) return 'already complete'
    return restartFromZero(url) // file changed; local partial is useless
  }
  if (res.status === 200) return restartFromZero(url) // server ignored Range
  // 206: append res.body to the partial file
}
```

For servers, return `Content-Range: bytes */<size>` on every 416, and on nginx cap pathological multi-range requests:

```nginx
location /downloads/ {
    max_ranges 1;   # 0 disables range support entirely; default is unlimited
}
```

If behind a CDN, purge the object after replacing a file in place, or better, publish new versions under new URLs (content-hashed filenames) so ranges can never straddle two versions.

## Reproduce it

```bash
# Ask for a range past the end
curl -i -r 999999999- https://example.com/files/small.txt
# HTTP/2 416
# content-range: bytes */1024

# wget resume on a finished file
wget -c https://example.com/files/small.txt
# HTTP request sent, awaiting response... 416 Requested Range Not Satisfiable
# The file is already fully retrieved; nothing to do.
```

## Related

- [206 Partial Content](https://howhttpworks.com/status-codes/206): the successful range response, plus `If-Range`.
- [Content-Range](https://howhttpworks.com/headers/content-range): syntax for both `bytes a-b/n` and `bytes */n`.
- [Range](https://howhttpworks.com/headers/range) and [Accept-Ranges](https://howhttpworks.com/headers/accept-ranges)
- [412 Precondition Failed](https://howhttpworks.com/status-codes/412): conditional request failed, a different mechanism.
- [400 Bad Request](https://howhttpworks.com/status-codes/400): syntactically broken requests.
