Back to HTTP Headers

Range transfer request

Requests a specific portion of a resource by byte range — enabling resumable downloads and video seeking.

What it does

Range asks the server to return only a specific portion of the resource rather than the whole thing. The server responds with 206 Partial Content and the requested slice, along with Content-Range describing where that slice fits in the full resource.

This powers resumable downloads (resume at byte 50MB instead of starting over) and video seeking (jump to 2:30 without downloading the first 2 minutes).

Syntax

Range: bytes=<range-start>-<range-end>
Range: bytes=<range-start>-
Range: bytes=-<suffix-length>
Range: bytes=<range1>, <range2>

Byte ranges are zero-indexed and inclusive on both ends:

Range: bytes=0-999        ← First 1000 bytes (bytes 0 through 999)
Range: bytes=500-999      ← Bytes 500 through 999 (500 bytes)
Range: bytes=9500-        ← From byte 9500 to end of file
Range: bytes=-500         ← Last 500 bytes
Range: bytes=0-499, 500-999  ← Two ranges (multipart response)

Server response

Successful partial response:

HTTP/1.1 206 Partial Content
Content-Range: bytes 0-999/8192
Content-Length: 1000
Content-Type: application/octet-stream

<1000 bytes>

Server doesn't support range requests:

HTTP/1.1 200 OK
<full resource>

A server can ignore Range and return the full resource with 200 OK. This is valid — the client must handle both 200 and 206.

Requested range unsatisfiable:

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

Resumable downloads

The classic use case:

  1. Client starts downloading a 100MB file
  2. Connection drops at 60MB
  3. Client checks what it has: 62914560 bytes (60MB)
  4. Client resumes: Range: bytes=62914560-
  5. Server responds 206 Partial Content with the remaining 40MB
  6. Client appends received bytes to existing partial file
GET /downloads/large-file.zip HTTP/1.1
Host: files.example.com
Range: bytes=62914560-

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

<remaining 40MB>

Video seeking

Media players use Range to implement seeking without buffering the entire video:

  1. User scrubs to 2:30 in a 10-minute video
  2. Player calculates the byte offset for that timestamp
  3. Player sends Range: bytes=<offset>-<offset+buffer>
  4. Server returns those bytes
  5. Player decodes and plays from that position

This is why you can seek in an online video almost instantly — the player is making range requests for just the bytes around your seek position.

Parallel download acceleration

Download managers split a file into N chunks and download them simultaneously:

GET /file.iso HTTP/1.1
Range: bytes=0-26214399          ← Thread 1: first 25MB

GET /file.iso HTTP/1.1
Range: bytes=26214400-52428799   ← Thread 2: next 25MB

GET /file.iso HTTP/1.1
Range: bytes=52428800-78643199   ← Thread 3: next 25MB

GET /file.iso HTTP/1.1
Range: bytes=78643200-           ← Thread 4: remainder

Four parallel connections, each downloading a quarter of the file. The client reassembles the chunks.

Multipart range requests

A single Range header can request multiple non-contiguous ranges:

Range: bytes=0-50, 100-150

HTTP/1.1 206 Partial Content
Content-Type: multipart/byteranges; boundary=3d6b6a416f9b5

--3d6b6a416f9b5
Content-Type: text/html
Content-Range: bytes 0-50/1270

<first 51 bytes>
--3d6b6a416f9b5
Content-Type: text/html
Content-Range: bytes 100-150/1270

<bytes 100-150>
--3d6b6a416f9b5--

Each part has its own Content-Range. The boundary separates them.

If-Range: safe resumption

When resuming a download, the resource might have changed since the partial download began. If-Range guards against resuming a stale partial file:

Range: bytes=62914560-
If-Range: "etag-of-original-resource"

If the ETag matches (resource unchanged): 206 Partial Content — resume. If the ETag doesn't match (resource changed): 200 OK with full new content — start over.

Common mistakes and gotchas

Not checking for 200 vs 206. A server can respond to a Range request with 200 OK (full resource) if it doesn't support range requests. Always check the status code — receiving 200 when expecting 206 means you got the full file, not a partial one.

Off-by-one byte ranges. Byte ranges are zero-indexed and inclusive. File of 1000 bytes: bytes 0-999. Requesting 0-1000 means requesting 1001 bytes from a 1000-byte file — 416 Range Not Satisfiable.

Not checking Accept-Ranges first. Before sending a Range request, check if the server sent Accept-Ranges: bytes on a prior HEAD or GET response. If absent or Accept-Ranges: none, range requests aren't supported.

Real-world examples

First chunk of a large download:

GET /videos/lecture.mp4 HTTP/1.1
Host: cdn.example.com
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
ETag: "abc123"

Suffix range (last 1KB for file footer):

GET /archive.zip HTTP/1.1
Range: bytes=-1024

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

FAQ

Do all servers support Range requests?

No. Static file servers (nginx, Apache, S3, CDNs) almost universally support range requests. Dynamic application servers may not — it depends on whether the framework/application implements partial content support. Check Accept-Ranges: bytes in the response.

Is Range the same as pagination?

No. Range is byte-level slicing of a single resource. API pagination (page 1, page 2, etc.) is application-level and typically uses query parameters (?page=2&per_page=20). Some APIs use Range header-based pagination (GitHub does), but this is non-standard and treats records as if they were bytes.

Can Range be used on POST requests?

Technically it can, but there's no defined semantics for it. Range is meaningful on GET and HEAD. POST creates or processes data rather than retrieving a resource slice.

Fun fact

Range requests were added to HTTP/1.1 specifically to solve dial-up-era problems: a 10MB software download that failed at 9.5MB meant starting over — every time. In 1997, on a 28.8 Kbps modem, that 10MB download took nearly an hour. Range requests meant a failed download could resume from where it stopped rather than starting from scratch. Today, with fast broadband, resumable downloads matter less for typical web browsing — but range requests are more important than ever for video streaming, where seeking to any position in a multi-GB video file must be instant.