Response header
< Accept-Ranges: bytesAccept-Ranges Header
Learn how the Accept-Ranges header tells clients whether your server supports partial content requests (byte ranges) for efficient downloads and streaming.
- Direction
- Response
- Category
- Range requests
- Spec
- RFC 9110 §14.3
On this page
TL;DR:
Accept-Ranges: bytestells clients they can request byte ranges, which is what makes resumable downloads and video seeking possible. It’s an advertisement: check for206on the actual response, feel free to send Range when the header is absent, and remember that adding the header doesn’t implement range handling.
What is Accept-Ranges?
Accept-Ranges lists the range units a server supports for this resource. In practice the only unit is bytes. RFC 9110 §14.3 lets clients send Range without ever seeing Accept-Ranges, and points out that seeing it once is no promise about later responses.
Syntax
Headers and outputs on this page are examples.
Accept-Ranges: bytes
Accept-Ranges: none
bytes advertises byte ranges. none asks clients not to try range requests. Leaving the header out is not a refusal, and sending none doesn’t change how the server actually handles Range.
How Accept-Ranges Works
A downloader can use the advertisement to decide whether to show a resume button. It still has to check the response to the actual Range request: 206 is partial content, 200 usually means Range was ignored, and 416 means the requested interval doesn’t exist.
Support varies by resource. A static file and a generated report on the same host are often served by different handlers with different range support. Treat each response’s Accept-Ranges as describing that resource, not the whole host.
Common Examples
A ten-byte static file might come back with:
Accept-Ranges: bytes
Content-Length: 10
ETag: "sample-v1"
To resume it, save the ETag and send it back in If-Range. Accept-Ranges says nothing about which version you have, so the validator is what protects you from stitching together two different files.
Server Implementation
For nginx static files, range handling is controlled by max_ranges. Its default has no range-count limit. To disable byte-range support for a location:
location /downloads/ {
max_ranges 0;
}
Put it inside your existing server block. Adding an Accept-Ranges: none header instead would leave the range filter running. The reverse holds too: adding Accept-Ranges: bytes to a dynamic response won’t slice its body or produce 206 and Content-Range.
Express supports byte ranges through its file-serving methods. With the deterministic fixture from the Range page, add a download route to an existing app:
app.get('/download', (req, res, next) => {
res.download('sample.txt', 'sample.txt', {
root: '/tmp/http-range-demo',
acceptRanges: true,
}, (error) => {
if (error) next(error);
});
});
res.download() uses file-transfer handling and offers acceptRanges, which defaults to true. Set it to false to turn off range support for that transfer. res.attachment() is a different thing: it only sets Content-Disposition and, with a filename, Content-Type. Call it before res.send() and you get a download prompt, but no range handling. A browser download prompt and a resumable response are separate features.
Django’s FileResponse is useful for streaming a binary file and setting download metadata:
from django.http import FileResponse
def download(request):
return FileResponse(open('/tmp/http-range-demo/sample.txt', 'rb'),
as_attachment=True, filename='sample.txt')
Django closes the file for you, so open it without a with block; one that ends before the response is sent would close the file too early. FileResponse sets length, type, and disposition when it can work them out, but its implementation doesn’t parse Range. Adding Accept-Ranges: bytes to this view would promise something it can’t do. If you need resume support, serve the file through a range-capable server or write the range handling yourself.
Getting Accept-Ranges right
A valid single-part partial response needs the right bytes and a matching Content-Range, so test the body length, not just the headers. A wrong advertisement may lead a downloader to try resuming, which is why downloaders have to handle a full 200 response safely anyway.
Compression and ranges can coexist: ranges count bytes of the encoded representation. What breaks is mixing the two, such as resuming against a gzip response using offsets from the decompressed file on disk. That corrupts the result.
For generated exports, ask whether the handler can produce the exact same bytes again and seek within them. A one-pass stream can’t serve a later request starting at an arbitrary offset, whatever the header says.
Testing Accept-Ranges
Use the nginx fixture to test a real GET:
curl -sS -D - -o /dev/null http://localhost:8080/sample.txt
curl -sS -D - --range 0-4 http://localhost:8080/sample.txt
Compare the advertisement with the actual status, Content-Range, and five-byte body. Use GET for this, since servers must ignore Range on HEAD.
Cross-origin JavaScript can read Accept-Ranges only if the response exposes it through CORS. On a response that already has CORS enabled, add:
Access-Control-Expose-Headers: Accept-Ranges, Content-Range, ETag
With the Express route above in place, test both the download metadata and the byte transfer:
curl -sS -D - --range 0-4 http://localhost:3000/download
Check Content-Disposition along with the 206, Content-Range, and the 01234 body. Then set acceptRanges: false, repeat the request, and confirm you now get the full response. Test the exact URL a downloader receives, redirects included: the headers on a redirect describe the redirect, not the file handler behind it.
This browser probe checks the response itself rather than the advertisement:
const response = await fetch('/download', {
headers: { Range: 'bytes=0-4' },
});
const bytes = new Uint8Array(await response.arrayBuffer());
console.table({
advertised: response.headers.get('accept-ranges'),
status: response.status,
interval: response.headers.get('content-range'),
receivedBytes: bytes.length,
});
Run it on the Express origin that serves the route. Five bytes back means success only if the status is 206 and the interval matches. A server can ignore Range even after advertising support, and a file that happens to be five bytes long would also return five bytes. Check all three together before you let a download UI append bytes.
Related Headers
Frequently asked questions
What is Accept-Ranges?
Accept-Ranges tells clients which range units the server supports for a resource. bytes means byte ranges work; none asks clients not to try. A missing header is not a refusal, so clients can still try Range.
Why is Accept-Ranges important?
Download managers and media players use it to decide whether to offer resume or seeking. Clients may send Range without it, though, and they should always check the actual response before stitching downloaded bytes together.
What does Accept-Ranges: none mean?
It asks clients not to send Range requests. It is advice only: sending it does not switch off range handling on the server, and clients that send Range anyway still get whatever the server does.
How do I enable Accept-Ranges on my server?
Use a range-capable handler, not just a header. nginx provides max_ranges to control its static-file range support; max_ranges 0 disables it.
Sources
Related
Content-Range Header
Learn how the Content-Range header indicates which portion of a resource is being sent in partial content (206) responses for range requests and streaming.
Access-Control-Max-Age Header
Learn how Access-Control-Max-Age specifies how long browsers can cache CORS preflight results. Reduce preflight requests and improve cross-origin performance.
Age Header
Learn how the Age header indicates how long a response has been cached in seconds. Understand cache freshness calculations and CDN behavior.
Alt-Svc Header: How Browsers Discover HTTP/3
Alt-Svc advertises an alternative protocol for an origin. How h3=":443"; ma=86400 moves browsers to HTTP/3, what clear does, and how HTTPS DNS records differ.