yt-dlp: HTTP Error 403, 429, and downloads that crawl
Three different failures that get treated as one. How to tell which you have, what fixes each, and why the most-repeated advice makes 429 permanently worse.
Three failures, one reputation:
ERROR: unable to download video data: HTTP Error 403: Forbidden
ERROR: Unable to download webpage: HTTP Error 429: Too Many Requests
…and the third, which prints no error at all: a download that runs at about the speed of the video itself.
They have different causes and different fixes, and the advice that helps one actively harms another. So the first job is telling them apart.
Which one do you have?
| What you see | What it is | Where to go |
|---|---|---|
403 immediately, on every video |
The URL was rejected outright | Below |
403 partway through a download |
The media URL expired mid-transfer | Below |
429 |
Rate limited, by IP address | Below |
| No error, ~1 MB/s, forever | Throttled, not blocked | Below |
403 Forbidden
On every video, straight away
Almost always a stale yt-dlp. YouTube changed how a media URL is signed, your build signs it the old way, and the server rejects it.
yt-dlp --version
brew upgrade yt-dlp # or however you installed it
This is genuinely the fix most of the time. It is boring, and it is why it gets skipped.
If you are up to date and still getting it everywhere, clear the cache — yt-dlp stores signature data, and stored data can go stale:
yt-dlp --rm-cache-dir
403 partway through
You are 60% into a large file and it dies. Different cause entirely.
YouTube’s media URLs are time-limited. They carry an expiry, usually around six hours. On a slow connection and a large 4K file, a download can genuinely outlive its own URL. The link is not wrong; it has aged out.
Resuming works, because yt-dlp fetches a fresh URL:
yt-dlp -c "URL"
-c continues a partial file instead of restarting. If it happens repeatedly
on the same file, take a smaller format — a 1.27 GB 2160p VP9 stream is a
different proposition from a 245 MB 1080p H.264 one:
yt-dlp -f "bestvideo[vcodec^=avc1][height<=1080]+bestaudio[ext=m4a]" "URL"
429 Too Many Requests
This one is about your IP address, not your software, and it is the one where the popular advice does real damage.
YouTube counts requests per address. Cross the line and you get 429 for everything — often for hours, sometimes longer. Metadata requests count. Listing a 300-video playlist is 300-ish requests before a single byte of video is fetched.
What actually helps
Stop, properly. Retrying is what extends the block. Every retry is another request against the count that is already over.
Slow down before you are blocked, not after:
yt-dlp --sleep-requests 1.5 --min-sleep-interval 5 --max-sleep-interval 15 \
--limit-rate 5M "URL"
--sleep-requestspauses between metadata requests. This is the one that matters for playlists.--min/--max-sleep-intervalpauses between downloads, randomised.--limit-ratecaps bandwidth. Pulling at your full line rate is itself a signal.
Slower than you would like. Considerably faster than being blocked for six hours.
For a big playlist, do it in pieces:
yt-dlp --playlist-items 1-25 --sleep-requests 2 \
--download-archive done.txt "URL"
The archive file records what completed, so tomorrow’s run skips it. That turns one blocked marathon into several quiet sessions. There is a whole piece on that pattern.
What makes it worse
A VPN. This is the single most repeated suggestion for 429 and it is close to the worst thing you can do. Commercial VPN exit nodes are shared by thousands of people, and a good number of them are also running downloaders. You are not moving to a clean address; you are moving to the dirtiest address available, one that is frequently already rate-limited before you arrive.
The same goes double for a cloud server. Datacentre ranges are the first thing scored badly.
Signing in. Passing cookies from a logged-in account attaches your behaviour to an identity that YouTube can act against, rather than to an address that resets. For 429 specifically it changes nothing about the limit — it only raises what a mistake costs.
Aggressive retries. --retries infinite on a 429 is a way of asking to
stay blocked.
The slow one
No error. The download simply crawls at roughly the video’s playback rate.
This is not 403 or 429. It is YouTube’s throttling, applied when the n
parameter challenge on the media URL goes unsolved — a different mechanism with
a different fix, covered in
nsig extraction failed.
The quick check: if yt-dlp -F "URL" on a video you know is 4K stops at 1080p,
that is the one you have.
The settings worth keeping
Put these in ~/.config/yt-dlp/config and stop thinking about it:
--sleep-requests 1
--min-sleep-interval 2
--max-sleep-interval 6
--retries 5
--fragment-retries 10
--continue
Polite by default, and slower only in the case where being fast was going to
cost you the afternoon. --fragment-retries matters more than it looks: large
downloads arrive in fragments, and a single failed fragment should not restart
the file.
The pattern behind all three
None of these errors is about permission in the human sense. They are about what a request looks like: how it is signed, how often it arrives, whether it solved the challenge. Nothing here is a lock being picked — it is a client either keeping up with a moving specification, or not.
That is also the honest reason the app I build updates its engine daily rather than shipping a fixed one. Most 403s are a stale binary, and the version of this problem you never see is the one that got fixed before you hit it.