If-Range conditional request
Makes a Range request conditional — returns the partial content only if the resource hasn't changed, otherwise the full resource.
What it does
If-Range makes a Range request conditional on a resource validator (an ETag or a Last-Modified date). It solves a real correctness problem: if you're resuming a partial download and the resource changed on the server in the meantime, blindly appending new bytes onto your old partial file produces silent, corrupted data — a Frankenstein file made of two different versions spliced together.
With If-Range, the server checks the validator before honoring the Range request:
- Validator matches (resource unchanged) →
206 Partial Contentwith just the requested range, safe to resume - Validator doesn't match (resource changed) →
200 OKwith the entire current resource, ignoringRangeentirely
This is different from If-Match/If-None-Match, which fail the request outright on a mismatch (412/304). If-Range never fails — it just falls back to sending everything, which is exactly the right behavior for a client that's ultimately fine with a full download, just hoping to avoid one.
Syntax
If-Range: "<etag-value>"
If-Range: <HTTP-date>
Takes exactly one validator — either a strong ETag or an HTTP-date matching Last-Modified — never both, and never a weak ETag (weak validators, prefixed W/, are explicitly disallowed here since a byte-range operation requires certainty the underlying bytes are identical, not just "semantically equivalent").
If-Range: "33a64df551425fcc55e4d42a148795d9f25f89d"
If-Range: Wed, 21 Oct 2015 07:28:00 GMT
How it's used with Range
Always paired with Range on the same request — If-Range without Range has no defined meaning:
GET /downloads/large-file.zip HTTP/1.1
Range: bytes=62914560-
If-Range: "abc123etag"
Resource unchanged — resume:
HTTP/1.1 206 Partial Content
Content-Range: bytes 62914560-104857599/104857600
ETag: "abc123etag"
Resource changed — client gets the full new version instead:
HTTP/1.1 200 OK
Content-Length: 104857600
ETag: "newetag456"
The client's job is to detect this: if it asked for 206 and got 200 back, its old partial data is stale and must be discarded — start the download over from the fresh full response rather than trying to reconcile.
Common mistakes and gotchas
Forgetting to check for a 200 response when expecting 206. This is the single most important thing to get right — if If-Range causes a fallback to 200 OK, the client must discard its existing partial file rather than appending the new full response onto it. A naive resumable-download implementation that only handles 206 and blindly appends whatever it gets will silently corrupt the file.
Using a weak ETag. Weak ETags (W/"...".) are explicitly not valid for If-Range per spec — servers should ignore a weak validator here (or per some implementations, may reject it), since a weak match means "equivalent enough to be cacheable," not "byte-for-byte identical," which is the actual guarantee range resumption needs.
Not sending If-Range at all on resumed downloads. Skipping it doesn't break anything outright, but it removes the safety check — a plain Range request will get honored (206) regardless of whether the resource changed since your partial download started, leaving you exposed to exactly the stale-splice problem If-Range exists to prevent.
Mismatching the validator type between requests. If you captured an ETag from the original response, send that ETag back in If-Range — not a Last-Modified date you constructed separately, and not an ETag from a different resource/request. The validator must be the one that actually accompanied the response your partial content came from.
Real-world examples
Download manager resuming after a dropped connection:
GET /iso/ubuntu-24.04.iso HTTP/1.1
Range: bytes=734003200-
If-Range: "d41d8cd98f00b204e9800998ecf8427e"
HTTP/1.1 206 Partial Content
Content-Range: bytes 734003200-1258291199/1258291200
ETag: "d41d8cd98f00b204e9800998ecf8427e"
File replaced between the original download and the resume attempt:
GET /iso/ubuntu-24.04.iso HTTP/1.1
Range: bytes=734003200-
If-Range: "d41d8cd98f00b204e9800998ecf8427e"
HTTP/1.1 200 OK
Content-Length: 1310720000
ETag: "9e107d9d372bb6826bd81d3542a419d6"
The client must discard its previously-downloaded 700MB and restart entirely — the ISO on the server is now a different build.
FAQ
What's the difference between If-Range and If-Match?
If-Match fails the whole request (412 Precondition Failed) if the validator doesn't match. If-Range never fails — on a mismatch it just serves the complete resource instead of the requested range, which is the right fallback behavior for resumable downloads rather than an outright error.
Can I use a weak ETag with If-Range?
No — If-Range requires a strong validator (either a strong ETag or a Last-Modified date). Weak ETags only guarantee semantic equivalence, not byte-identical content, which isn't a strong enough guarantee for safely splicing byte ranges together.
Do I need to send If-Range on every Range request?
Only when you're resuming a previously-started partial download and want protection against the resource having changed in between. For a fresh Range request with no prior partial data to protect (like a video player's first seek), there's nothing to guard against changing mid-download, so If-Range isn't necessary.
What happens if the server doesn't support If-Range?
Servers that don't recognize If-Range should ignore it and process the request as an unconditional Range request — meaning they'll return 206 Partial Content regardless of whether the resource actually changed, silently reintroducing the stale-splice risk If-Range exists to prevent. This is one more reason to also verify the returned ETag/Last-Modified matches your expectation client-side as a backstop.
Fun fact
If-Range is a good example of a header designed around graceful degradation rather than strict correctness enforcement — most conditional headers (If-Match, If-None-Match, If-Unmodified-Since) are built to reject a request outright on failure. If-Range deliberately never does that: worst case, you just end up re-downloading the whole file instead of the small remaining chunk, which is exactly the outcome you'd have gotten anyway if Range support didn't exist at all. It fails safe by falling back to the pre-Range-requests behavior of HTTP, rather than failing hard.