The Day I Stopped Downloading the Internet - A Story About HTTP Range Requests

It started with a complaint that sounded harmless.

“The video player is broken again.”

We run a simple web app: users upload recordings, and other users watch them. Nothing fancy. A <video> tag, a CDN in front, and a backend that returns the file. It had been “fine” for months.

Until it wasn’t.

The symptom that didn’t make sense

The bug reports were weirdly inconsistent:

  • On fast Wi-Fi: “works most of the time”
  • On mobile: “loads forever”
  • Scrubbing (jumping ahead): “sometimes resets to the beginning”
  • Some browsers: “it just gives up”

I did the usual ritual:

  1. Check server logs
  2. Check CDN logs
  3. Check the player code
  4. Blame the browser (quietly)

Everything looked… normal. Requests were coming in. Responses were going out. Bandwidth usage was high, but that’s expected with video, right?

Then I watched a session in slow motion (DevTools → Network), and I saw it.

Every time the user dragged the timeline forward, the browser did not ask for the whole file again.

It asked for a slice.

And my server… ignored it.

The strange header I’d been ignoring

The request had this header:

Range: bytes=10485760-

Translation: “Give me the file starting at byte 10,485,760 until the end.”

And sometimes it looked like:

Range: bytes=0-1048575

Translation: “Give me the first megabyte.”

The browser wasn’t being dramatic. It was being efficient. It wanted only what it needed to start playback quickly or to jump ahead.

But my backend always replied with:

  • 200 OK
  • The entire file
  • From the beginning
  • Every time

Which meant: scrubbing caused a full re-download. On mobile, that felt like a soft denial-of-service against the user’s data plan.

The moment it clicked

I found a line in the player’s error output:

"Media resource does not support byte-range requests."

That was the turning point.

The browser expected the server to support partial content. Without it, the browser’s behavior becomes clumsy:

  • it can’t efficiently seek
  • it can’t resume downloads reliably
  • it may buffer way more than needed
  • some players will just refuse

And that’s when I met my new best friend:

HTTP Range Requests.

What Range Requests actually are (in human terms)

A range request is a client saying:

“Don’t send me the whole thing. Send me only this part.”

This is huge for:

  • video/audio streaming
  • resumable downloads
  • large files (archives, installers)
  • previewing content (PDFs, media metadata, etc.)

The request uses the Range header:

Range: bytes=start-end

Examples:

  • bytes=0-999 → first 1000 bytes
  • bytes=1000- → everything after byte 1000
  • bytes=-500 → last 500 bytes (yes, that’s a thing)

If the server supports it, it answers with:

  • Status: 206 Partial Content
  • Header: Content-Range: bytes start-end/total
  • Header: Accept-Ranges: bytes
  • Body: only that slice of the file

If it doesn’t support it, it might reply with:

  • 200 OK (and the full file) — what I was doing 😬
  • or 416 Range Not Satisfiable if the range is invalid

The fix that felt like magic

I implemented proper Range handling in the file endpoint.

The changes were conceptually simple:

  1. Read the Range header
  2. Validate the requested range against the file size
  3. Return 206 with the correct chunk
  4. Include Content-Range, Accept-Ranges, and the correct Content-Length
  5. Stream the file slice (don’t load it all into memory)

I deployed it, refreshed the player, and tried the exact same thing that used to fail: scrub to the middle of the video.

This time, the network panel showed a clean pattern:

  • small initial fetch
  • targeted chunk fetches when seeking
  • no more full-file redownloads

The result wasn’t subtle. It was immediate:

  • playback started faster
  • scrubbing stopped freezing
  • mobile stopped burning data
  • the server’s bandwidth dropped (a lot)

It felt like I’d upgraded the internet without telling anyone.

A quick mental model: why players love Range

Video files aren’t just “a stream of pixels.” They often contain metadata and indexes that help seeking. Many players grab:

  • a small chunk near the start (to parse headers)
  • sometimes a chunk near the end (to find indexes)
  • then specific ranges as the user watches/seeks

If you force them to download everything from byte 0 every time, they either:

  • waste bandwidth, or
  • fail to seek, or
  • refuse to play nicely

Range requests are the difference between:

“download the whole movie to jump to minute 37”

and

“fetch exactly what I need for minute 37.”

Common gotchas (the ones that bite at 2am)

1. You must return 206

If you return 200 with partial bytes, clients may behave unpredictably.

2. Content-Range must be correct

Format matters:

Content-Range: bytes 1000-1999/12345678

3. Use Accept-Ranges: bytes

This tells clients “yes, you can do this.”

4. Don’t forget caching/CDN behavior

CDNs can cache range responses, but configuration varies. Some require explicit settings.

5. Multi-range requests exist

A client can ask for multiple chunks:

Range: bytes=0-99,200-299

Many servers choose to ignore multi-range or handle it carefully. For media streaming, single-range is most common.

The “why didn’t I know this earlier?” ending

After the fix went out, I did what we all do when we’re slightly embarrassed:

I searched my logs for how long this had been happening.

The answer was: months.

The browsers had been politely asking, again and again:

“Can I please just have bytes 10MB and onward?”

And my server kept replying:

“Here is the entire file. From the beginning. You’re welcome.”

Range requests didn’t just fix a bug.

They fixed a misunderstanding between my server and the modern web.

And now, whenever I build anything that serves large files, I ask myself one question early:

“What happens when the client only wants a slice?”

Because sometimes the best performance optimization isn’t compression, caching, or faster disks.

Sometimes it’s simply… not sending the whole internet.