Back to HTTP Headers

Content-Range content response

Indicates where a partial response body fits within the full content — sent alongside 206 Partial Content responses.

What it does

Content-Range tells the client where the partial response body fits within the complete resource. It's the server's reply to a Range request — when a client asks for part of a resource, the server sends the slice along with Content-Range describing which bytes were sent and how big the total resource is.

You only see Content-Range on responses with status 206 Partial Content (and occasionally 416 Range Not Satisfiable). It never appears on full 200 OK responses.

Syntax

Content-Range: <unit> <range-start>-<range-end>/<total-length>
Content-Range: <unit> <range-start>-<range-end>/*
Content-Range: <unit> */<total-length>
  • <unit> — always bytes in practice
  • <range-start> and <range-end> — zero-indexed byte positions (inclusive on both ends)
  • <total-length> — complete resource size, or * if unknown
  • */<total-length> — used on 416 responses when no range can be satisfied

Examples:

Content-Range: bytes 0-999/5000
Content-Range: bytes 4000-4999/5000
Content-Range: bytes 21010-47021/47022
Content-Range: bytes 0-1023/*
Content-Range: bytes */47022

How ranges work end-to-end

The full flow:

  1. Client sends Range: bytes=0-1023 (requesting first 1024 bytes)
  2. Server responds with 206 Partial Content + Content-Range: bytes 0-1023/8192
  3. Client gets 1024 bytes, knows the total is 8192 bytes
  4. Client can request the rest in subsequent requests or resume later

The Range header is the request; Content-Range is the response. Accept-Ranges: bytes is the server's advertisement that it supports range requests at all.

Practical use cases

Resumable downloads. A download manager pauses at 50MB, then resumes by sending Range: bytes=52428800-. The server responds with Content-Range: bytes 52428800-104857599/104857600 — confirming the slice and total size.

Video/audio streaming. Media players request ranges to seek to a specific timestamp without downloading the entire file. Content-Range tells the player how the chunk maps to the full file timeline.

Progressive downloads. Large file hosting services use range requests to serve multiple parallel chunks of the same file simultaneously, then reassemble them — faster than a single sequential download.

Multipart range responses. If a client requests multiple non-contiguous ranges in one request (Range: bytes=0-499, 700-999), the server can respond with Content-Type: multipart/byteranges where each part has its own Content-Range.

How it interacts with other headers

Content-Range always pairs with Range (the request trigger) and Accept-Ranges (the server advertisement). The three together form the range request mechanism.

Content-Length on a 206 response should reflect the size of the partial body being returned, not the full resource size. Content-Range carries the full resource size separately.

ETag and Last-Modified matter for range requests too — if the resource changed between the initial request and a resume attempt, the server should return 416 or a full 200 rather than continuing with stale ranges. Clients should send If-Range to guard against this.

Common mistakes and gotchas

Off-by-one on the range values. Byte ranges are zero-indexed and inclusive on both ends. A 1000-byte resource has bytes 0–999. Content-Range: bytes 0-1000/1000 is wrong — the last byte is 999. This is a frequent source of bugs when implementing range responses from scratch.

Not sending Accept-Ranges first. Clients shouldn't assume range support. Always send Accept-Ranges: bytes in responses to signal support. Without it, many clients won't even attempt range requests.

Returning 200 instead of 206. If a client sends a Range header and you respond with 200 OK + the full body, the client's download manager or media player won't know it got a partial response — it expects 206. Some clients handle this gracefully; others break.

Forgetting If-Range. When resuming a download, if the resource has changed since the original request, you should not serve the old range of a new file. Clients should send If-Range: <etag> or If-Range: <last-modified>. If the precondition fails, return the full resource with 200 instead of the requested range with 206.

Real-world examples

Initial range request and response:

GET /videos/tutorial.mp4 HTTP/1.1
Range: bytes=0-1048575

HTTP/1.1 206 Partial Content
Content-Type: video/mp4
Content-Range: bytes 0-1048575/94371840
Content-Length: 1048576
Accept-Ranges: bytes

Resume a download:

GET /downloads/package.zip HTTP/1.1
Range: bytes=52428800-
If-Range: "abc123etag"

HTTP/1.1 206 Partial Content
Content-Range: bytes 52428800-104857599/104857600
Content-Length: 52428800

Unsatisfiable range (416 response):

GET /file.txt HTTP/1.1
Range: bytes=9999-10000

HTTP/1.1 416 Range Not Satisfiable
Content-Range: bytes */500

FAQ

What's the difference between Content-Range and Range?

Range is the request header — the client sends it to ask for a specific portion of the resource. Content-Range is the response header — the server sends it to describe which portion it's returning and the total resource size.

Is Content-Range only for bytes?

The spec allows other range units, but bytes is the only one defined and widely implemented. You'll never encounter a non-bytes Content-Range in practice.

Can a server ignore Range requests?

Yes. A server can respond to any Range request with a full 200 OK response and the complete body. This is valid but suboptimal — it means the client can't resume downloads or seek in media. Returning Accept-Ranges: none explicitly tells clients not to bother sending range requests.

Does Content-Range work with compressed responses?

It's complicated. Range requests work on the raw bytes of the resource. If the server is compressing responses dynamically, ranges don't align with compression boundaries. Best practice: serve static files with range support disabled on dynamic compression, or pre-compress and serve the compressed file as the base resource with ranges applied to the compressed bytes.

Fun fact

Range requests were originally designed with HTTP/1.1 for FTP-style resumable downloads — a critical feature in the dial-up era when a 10MB download could take 30 minutes and a dropped connection meant starting over. Today they're equally essential for video streaming: every time you scrub a video timeline on a streaming service, you're almost certainly triggering range requests under the hood.