ClipLatch
·by Tyler Brennan·performancetroubleshootingffmpeg

Where the seconds go when an Instagram download feels slow

Downloads that feel slow are rarely slow where you think. A Reel that takes ten seconds from click to file spends a fraction of that transferring bytes to you — the rest goes to a proxy handshake, a manifest fetch, two streams pulled from Instagram's CDN, and a mux. Here's the whole ledger, including the queue that can make it stop entirely.

The ledger

A Reel download has five stages, and only the last one involves your connection.

Parse. We ask Instagram what the post contains, through a residential proxy. The proxy handshake alone is the single largest fixed cost in the chain — roughly 900 ms of the 1.3 seconds a typical parse takes, measured against a direct-from-VPS control in our residential proxy writeup.

Manifest. Reels arrive as DASH, so we read an MPD to find the representations — the format and what's inside it is taken apart in inside Instagram's DASH manifest. Small file, one round trip, still through the proxy.

Fetch. Video and audio are separate streams and both have to come down before the file is whole. This is where most of the wall-clock lives on a long Reel.

Mux. ffmpeg stitches the two with -c copy — no re-encoding, so it's I/O bound rather than CPU bound. Then bytes go to you.

Your progress bar is lying, and that's on purpose

We emit fragmented MP4 (frag_keyframe+empty_moov), which means ffmpeg can push bytes to your browser as it produces them instead of writing a complete file and rewriting the header at the end.

The upside is that your download starts almost immediately — no staring at a spinner while a file assembles server-side.

The cost is that your browser has no idea how big the result will be, because nobody does until the mux finishes. So you get a progress indicator that counts bytes without a total, or one that guesses and then jumps. A download that appears stalled at an odd percentage is very often finishing normally.

Judge progress by whether the byte count is still climbing, not by the bar.

The one case where it genuinely stops

We run at most six ffmpeg processes at once across the whole site. Each takes about 80 MiB of RAM, and the container has a 4 GiB budget it has to share with the app itself — an unbounded spawn on a traffic spike is how you OOM a box and take everything down with it.

Request seven arrives, waits for a slot, and waits up to 25 seconds. If nothing frees up, you get an HTTP 503 saying the server is merging too many videos right now, with a retry-after of 30 seconds.

That message is honest and it means exactly what it says. Waiting half a minute genuinely resolves it. Clicking Download four more times does not, and adds four more requests to the same queue you're stuck behind.

Twenty-five seconds is chosen to stay well under nginx's 60-second read timeout. Past that boundary you'd get a gateway error instead of our message, which tells you nothing about what to do next.

Rate limits look like slowness and aren't

We allow 20 requests a minute and 100 an hour from one IP. Cross either and you get a 429 carrying a Retry-After header — a standard HTTP mechanism, not something we invented — telling you exactly when the window reopens.

Those numbers are generous for a person and tight for a script. Bulk work is the usual way to trip them — 30 URLs pasted at once, each one a parse, some of them retried.

Instagram's own limits sit above ours and are stricter, less documented, and enforced per exit IP. When their limiter engages, symptoms shift from slow to wrong: partial carousels, empty media responses, sudden login walls on posts that were public a minute ago.

The tell that separates the two: our limiter tells you the wait in seconds. Instagram's doesn't tell you anything at all — those symptoms are catalogued in the six reasons downloaders stop working.

What actually makes it faster

Pick a lower tier. 720p is roughly half the bytes of 1080p on most Reels and visually identical on a phone screen. The largest lever available, and the one people skip because the bigger number looks better.

Download audio alone when audio is what you want. Extracting the AAC track avoids pulling the video stream entirely — for a music clip that's a fraction of the transfer.

Space out bulk work. Our bulk tool runs two URLs concurrently on purpose; the limit isn't timidity, it's the fastest rate that doesn't get the exit IP flagged. Pasting 30 links and walking away beats babysitting and retrying.

Retrying immediately is the one move that reliably makes things worse. A slow response usually means something upstream is already throttling, and a second request is evidence for whatever decided to throttle you.

Things that won't help, despite sounding like they should

A faster home connection. You're not the bottleneck — a 60-second 720p Reel is 15-25 MB, which any modern connection moves in under a second. The other nine seconds happened before your browser saw a byte.

A VPN. It adds a hop and puts your requests behind a datacenter IP for the parts you control, which is the exact category Instagram's CDN treats worst.

A different browser. The work is server-side. Chrome and Safari get identical bytes on identical timing.

Trying again in a different tab, which is our favourite because it feels so productive. Same queue, same limiter, same upstream, one more request in line ahead of the one you already have waiting.

FAQ

Why does the first download of the day feel slower?
We cache parse results for ten minutes, so a post someone already requested resolves without touching Instagram at all. Your first request for a given post pays the full proxy handshake and manifest walk; a repeat within the window skips straight to the download.
Is a 503 an outage?
Not usually. Our 503 on the merge endpoint is a queue signal — six ffmpeg slots are busy and yours waited 25 seconds without one opening. The site is up, the pipeline is up, you arrived during a burst. The retry-after header says 30 seconds and means it.
Would paying for a faster server fix this?
Only the mux stage, which is already the cheapest part. Most of the wall-clock is a proxy handshake plus fetching from Instagram's CDN — neither of which is bounded by our hardware. More cores would raise the concurrent ffmpeg ceiling and shorten queue waits during bursts, which matters at scale and not to any individual download.
Does downloading in bulk go faster than one at a time?
Per file, no — bulk runs two at a time deliberately, which is roughly what a residential exit IP tolerates without getting flagged. Per session it wins easily, because the tool waits and retries for you instead of you watching each one. The constraint is Instagram's patience, not our throughput.

Related tools