Accept-Ranges header explained: bytes vs none
Before a downloader tries to resume, it looks for one small promise in the response headers: Accept-Ranges: bytes. Here is what that promise does and does not guarantee.
What you see
$ curl -sI https://example.com/big.iso
HTTP/2 200
content-type: application/octet-stream
content-length: 734003200
accept-ranges: bytes
etag: "2bc0-5f1a"
curl -I sends a HEAD request, which returns the headers a GET would. Some servers answer HEAD differently from GET, so if the result surprises you, check a real GET with curl -s -o /dev/null -D - URL.
The three possible answers
| Header | Meaning | What a downloader does |
|---|---|---|
Accept-Ranges: bytes | Byte ranges are supported for this URL | Can resume, fetch pieces in parallel, or seek inside a video |
Accept-Ranges: none | The server explicitly will not serve ranges | Should not try; a broken download has to start over |
| No header | No promise either way | May try a Range anyway and fall back if it gets a full 200 |
Why the header and the behaviour can disagree
- On-the-fly compression. If the server gzips or brotli-compresses the response as it sends it, the byte offsets change from request to request, so many servers drop range support for those responses. nginx’s gzip filter, for example, removes
Accept-Rangeswhen it compresses. - Generated responses. A file streamed out of an app (a report built on demand, a ZIP made per request) usually has no fixed length, so ranges are not offered.
- Something in between. A proxy, CDN or security layer can strip the Range header on the way in or the Accept-Ranges header on the way out. Test the origin directly if you can.
- HEAD vs GET. A few frameworks build HEAD replies separately and forget the header.
The real test is to ask for a range and see what comes back: see Range request returns 200 instead of 206.
Turning it on
- Static files on nginx, Apache, Caddy and most CDNs support byte ranges by default.
- Node / Express:
express.staticandres.sendFilehandle ranges (theacceptRangesoption is on by default). A hand-writtenfs.createReadStream(file).pipe(res)does not; you would have to parse Range yourself. - Python: the built-in
python -m http.serverignores Range. Serve big files from nginx or a framework helper that supports it. - Keep the bytes stable: the same URL must return the same file, and an
ETagorLast-Modifiedlets clients check that withIf-Range.
Related
HTTP 206 Partial Content · Content-Range header · Range request returns 200 · Download won't resume · 416 Range Not Satisfiable · Wirebook: how resuming works
Questions
What does Accept-Ranges: bytes mean?
The server is saying it accepts byte-range requests for this URL, so a client can ask for part of the file and get 206 Partial Content back.
What does Accept-Ranges: none mean?
The server explicitly does not support range requests for this resource, so an interrupted download cannot be resumed and has to start again.
If there is no Accept-Ranges header, are ranges unsupported?
Not necessarily. The server has made no promise. A client can still send a Range header and must accept a full 200 if the server ignores it.
How do I check Accept-Ranges with curl?
Run curl -sI URL for the headers, or curl -s -o /dev/null -D - -H 'Range: bytes=0-99' URL and look for a 206 status and a Content-Range header.
Why did Accept-Ranges disappear when compression was turned on?
Compressing on the fly changes the bytes on every request, so servers such as nginx stop offering ranges for compressed responses.
Published .