# Yt Dlp Troubleshooting

> Diagnose and fix yt-dlp failures on YouTube, TikTok, Instagram and generic sites in 2026. Use when a download or metadata fetch fails with errors like "Sign in to confirm you're not a bot", "HTTP Error 403/429", "Requested format is not available", "Signature solving failed" / "nsig extraction failed", "No supported JavaScript runtime", "Unable to extract webpage video data", "empty media response", "There is no video in this post", "Unsupported URL", or when it works on a laptop but not in Docker/CI/cloud. Explains PO tokens, the JS runtime requirement, TLS impersonation, cookies, proxies, rate limits, and when the honest answer is "this IP will never work".

- Skill: `franciscobmacedo/yt-dlp-troubleshooting` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add franciscobmacedo/yt-dlp-troubleshooting`
- Raw SKILL.md: https://api.skillmd.com/api/skills/franciscobmacedo/yt-dlp-troubleshooting/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- License: MIT
- Author: franciscobmacedo (https://skillmd.com/u/franciscobmacedo)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/franciscobmacedo/yt-dlp-troubleshooting

---


# yt-dlp troubleshooting (2026)

Most yt-dlp "bugs" are one of five things: an old build, no JavaScript runtime, a missing PO token, a missing TLS-impersonation dependency, or an IP that the platform has decided is a bot. Work through §1 before touching anything else, then match the exact error text in §2.

Sources: the yt-dlp README and wiki (Extractors, FAQ, PO-Token-Guide, EJS pages), maintainer statements on GitHub issues, the bgutil provider README, and the author's experience running yt-dlp at volume. Quotes are verbatim where marked. Open `references/error-table.md` whenever a version number, a client name, or a flag matters (the user's build is old, you're recommending a runtime or provider version); it carries the sources and the date each fact was checked. This area changes monthly.

## 1. Always do this first (fixes most reports)

```bash
yt-dlp --version                     # stable is dated YYYY.MM.DD; anything >90 days old is suspect
pip install -U "yt-dlp[default,curl-cffi]"   # pip users. [default] = yt-dlp-ejs + deps; curl-cffi = impersonation
yt-dlp --update-to nightly           # binary users. "-U" stays on your channel; nightly is the maintainers' recommended channel
yt-dlp -v "URL" 2>&1 | head -40      # verbose header: version, runtimes found, PO-token providers, impersonate targets
```

Read the verbose header. You want to see a JS runtime (`deno` or `node`), and for YouTube on a server, a PO Token Provider line. If a maintainer asks for a bug report they want `-v` output and, for page-parsing errors, `--write-pages` dumps (never combine with `--cookies*`, the dumps leak session data).

Two structural facts that explain most of §2:

- **YouTube needs a JavaScript runtime since 2025.11.12.** Without one, yt-dlp still "works" but format availability is "limited, and severely so in some cases" (maintainer announcement, issue #15012). Install `deno` (only runtime enabled by default; ≥2.3.0) or enable node ≥22 with `--js-runtimes node`. Bun is deprecated.
- **YouTube is enforcing PO tokens.** A Proof-of-Origin token is generated by BotGuard in a browser; yt-dlp cannot mint one itself, so it needs a provider plugin. Without it, formats that need a GVS token are skipped (`MISSING POT` in `-F` output) and you may get 403s. The wiki's recommended setup: "Use a PO Token Provider plugin to provide the `mweb` client with a PO Token for GVS requests."

## 2. Error → cause → fix

| Error text (search for the quoted part) | What it means | Fix |
|---|---|---|
| `Sign in to confirm you're not a bot` | YouTube's bot-wall. Nearly certain from datacenter/cloud IPs; also hits home IPs after bursts | On a laptop: export cookies the *right* way (§3). On a server: PO-token provider (§4) **and** a clean egress IP; maintainers say DC IPs are "susceptible to being blocked. We cannot help with this." Cookies from a real account on a server risk that account |
| `YouTube is requiring a captcha challenge before playback` | Same wall, captcha variant | Same as above; solve it in a browser on the same IP, then pass cookies |
| `HTTP Error 403` on `googlevideo.com/videoplayback` | IP-based block on the media URLs, "regardless of client and protocol" (coletdjnz). Or a missing GVS PO token | Back off (`-t sleep`), change IP; make sure the same IP does extraction and download (media URLs are IP-bound, 6h expiry); add a PO-token provider |
| `Some formats may be missing`, `Signature solving failed`, `n challenge solving failed`, `No supported JavaScript runtime could be found` | No usable JS runtime / outdated `yt-dlp-ejs` | Install deno ≥2.3 (or node ≥22 + `--js-runtimes node`), update yt-dlp. Old builds print `nsig extraction failed` for the same problem |
| `Requested format is not available` | Generic: your `-f` selector matched nothing. On YouTube in 2026 this usually means formats were dropped (no JS runtime, missing PO token, or IP block) | `yt-dlp -F URL` to see what survived; fix the underlying row; use `-f "bv*+ba/b"` style fallbacks |
| `... client https formats require a GVS PO Token which was not provided` | Exactly what it says; those formats are skipped | Install a PO-token provider (§4) or pass `--extractor-args "youtube:po_token=CLIENT.gvs+TOKEN"` |
| `This content isn't available, try again later` + "rate-limited by YouTube for up to an hour" | Session rate limit. Wiki: guests ~300 videos/hour (~1000 page/player requests), accounts ~2000/hour | `-t sleep` preset, or `--sleep-requests 1 --sleep-interval 5 --max-sleep-interval 10`; wait an hour |
| `Unable to download video subtitles for 'xx-yy': HTTP Error 429` | Caption endpoint rate limit. Maintainer (bashonly, #13831): "Manual subtitles and original language automatic captions are not affected... Only subtitles/captions that have been automatically translated into another language are affected." | Request only native tracks (`--list-subs`; avoid `en-fr` style translated codes), `--sleep-subtitles 60`, or fresh browser cookies from a session that just loaded the translated subs. A PO token does **not** help. Add `-i` so the video still downloads |
| `HTTP Error 429: Too Many Requests` (general) / `402 Payment Required` | FAQ: "the service is blocking your IP address because of overuse" | Slow down, solve the captcha in a browser and pass cookies, or change egress IP |
| `Login with OAuth is no longer supported` / `Login with password is not supported for YouTube` | Those auth paths are dead | Cookies only (§3) |
| `HTTP Error 400: Bad Request` right after `--cookies FILE` | Cookie file isn't Netscape format / wrong newlines | Re-export with a "Get cookies.txt LOCALLY"-type extension; first line must be `# Netscape HTTP Cookie File` |
| `[TikTok] Unable to extract webpage video data` | TikTok served a JS challenge/captcha page instead of the video. Fixed in yt-dlp 2026.01.29 (native challenge solver); recurs when automated bursts trigger a captcha | Update. If it persists: made-up `--user-agent abc` (maintainer workaround), or **session** cookies via `--cookies FILE` exported after loading the video (not `--cookies-from-browser`, which can't see session cookies). `--xff US` for geo blocks |
| `[TikTok] ... Unexpected response from webpage request` | A TikTok bot heuristic introduced mid-2026; partly about the user agent, partly about the IP | Present a desktop Chrome user agent; if it persists, the IP is being judged too |
| `The extractor is attempting impersonation, but no impersonate target is available` | `curl_cffi` not installed. TikTok always impersonates; Instagram requires it for anonymous requests since 2026.07 | `pip install "yt-dlp[curl-cffi]"` or use a binary that bundles it (the Unix zipimport `yt-dlp` binary and Windows x86 don't) |
| `[Instagram] Instagram sent an empty media response` | Post isn't accessible logged-out (private/restricted), or anonymous request lacked a browser fingerprint | Check the post in an incognito window. If it loads: install curl_cffi. If it doesn't: it needs cookies (account risk) or is genuinely unavailable |
| `There is no video in this post` / `No video formats found` (Instagram) | It's a photo post or carousel; metadata, caption and images were extracted fine, yt-dlp just errors on the absence of video | `--ignore-no-formats-error` and read `thumbnails` from `-J` output |
| `redirected to the login page. You have exceeded the rate-limit for accessing posts anonymously` (Instagram) | Per-IP anonymous rate limit | Wait, change IP, or cookies |
| `This content isn't available to everyone` / `can't be seen by certain audiences` (Instagram) | Audience/age restriction on the post. Reproduces from every IP; no logged-out fetch clears it | Stop retrying. Cookies from an eligible account or give up |
| `Video unavailable` / `Private video` / `This video has been removed` / `HTTP Error 404` | Content gone or private. **Caution:** YouTube's wall sometimes serves fake "not available" to IPs it dislikes | If it plays in a browser from a clean IP, treat as a wall; otherwise it's dead |
| `Unsupported URL` | No extractor matched and the generic extractor found no embeds. Or you passed a channel/playlist/profile page | Pass a single post URL. For an article with an embedded player: `--extractor-args "generic:impersonate"` may get past a Cloudflare challenge; if the page is the content, don't use yt-dlp at all |
| `Got HTTP Error 403 caused by Cloudflare anti-bot challenge` (generic) | Page is behind Cloudflare's challenge | `--extractor-args "generic:impersonate"` with curl_cffi installed |
| `blocked due to its TLS fingerprint` / `install a required impersonation dependency` (Vimeo and others) | The HTTP client's TLS fingerprint is the problem, not the IP. Rotating proxies is provably futile here: a CONNECT proxy tunnels TCP, so the origin sees the identical ClientHello from every proxy | Install curl_cffi; `--impersonate chrome` |
| `Cannot parse data` (Facebook story.php etc.) | Deterministic extractor bug, identical on every IP | Update yt-dlp; file an issue; don't rotate proxies |
| `This video is only available for registered users` (Facebook) | Login wall, per post. Changing IP does not help | Cookies or give up |
| `Unable to connect to proxy` / `Tunnel connection failed` / `ProxyError` | Your proxy, not the site | Fix the proxy; don't score the site as blocked |

## 3. Cookies, done the way the wiki says

The wiki (Extractors → "Exporting YouTube cookies") is specific because YouTube rotates cookies on open tabs:

1. Open a **private/incognito** window, log in to YouTube.
2. In that same tab, navigate to `https://www.youtube.com/robots.txt` (must be the only private tab open).
3. Export `youtube.com` cookies with a Netscape-format extension ("Get cookies.txt LOCALLY" on Chrome, "cookies.txt" on Firefox; the old "Get cookies.txt" extension was reported as malware).
4. **Close the private window** so the session is never touched again.

Don't use `--cookies FILE --cookies-from-browser BROWSER` together to export; it grabs your regular profile's cookies, not the incognito session. And read the warning: "By using your account with yt-dlp, you run the risk of it being banned (temporarily or permanently)... consider using a throwaway account." Cookies are "only necessary for content that requires an account" (private playlists, age-restricted, members-only); they are a workaround for bot-walls, not the intended use.

## 4. PO tokens on a server (the setup that actually works)

Recommended provider: `bgutil-ytdlp-pot-provider` (maintained by a yt-dlp maintainer). Two pieces that must be the **same version**: an HTTP server that mints tokens and a yt-dlp plugin that asks it.

```bash
# 1. Provider server (Docker; port 4416). Needs --net=host if yt-dlp uses a local proxy.
docker run --name bgutil-provider -d --init -p 4416:4416 brainicism/bgutil-ytdlp-pot-provider:1.3.2

# 2. Plugin, pinned to the same version
pip install "bgutil-ytdlp-pot-provider==1.3.2"

# 3. Verify: the verbose header must list the provider
yt-dlp -v "URL" 2>&1 | grep -i "PO Token Providers"
#   [debug] [youtube] [pot] PO Token Providers: bgutil:http-1.3.2 (external), ...

# 4. Point at a non-default address if needed
yt-dlp --extractor-args "youtubepot-bgutilhttp:base_url=http://127.0.0.1:4416" "URL"
```

Script mode (`youtubepot-bgutilscript:server_home=...`) spawns a Node process per call; the README says it is "NOT recommended for high concurrency usage". Tokens are bound to the video ID now, so manual extraction "is no longer recommended". Client choice matters: `mweb` needs a GVS token; `web_safari` gives HLS formats that don't; `tv` needs none but DRMs everything without cookies; `android`/`ios` need GVS **or** player tokens and don't take cookies. `--extractor-args "youtube:player_client=mweb"` is the wiki's suggestion when defaults fail.

A PO token makes traffic look legitimate; it does **not** launder a bad IP. bgutil README: "Providing a PO token does not guarantee bypassing 403 errors or bot checks". Set expectations plainly: on a **single** cloud IP, update + runtime + provider fixes some setups and not others, and an IP that works today can be walled later. If you only have one IP and it stays walled after §4, no flag will change that.

Subtitles-only jobs (`--skip-download --write-auto-subs`) do **not** dodge the wall: the bot check happens on the watch/player request that precedes any caption download, so the same fix applies. Native-language auto-subs need no PO token for subs; only the `web` client's subs context is token-gated.

On a server this means running the provider as a sidecar on the same network as yt-dlp and pinning the plugin and server versions in lockstep, and then keeping that pair, the JS runtime and yt-dlp itself current as YouTube changes. Getting it stable is a maintenance commitment, not a one-off install.

## 5. Proxies and IPs, honestly

- yt-dlp's docs contain no residential-vs-datacenter guidance; the position in issues is "there's nothing we can do about it" for DC IPs. In practice: datacenter IPs get walled, tokens help, and IPs burn. A proxy pool is a consumable, not a fix.
- Some failures are **not IP problems** and rotating makes them worse: TLS-fingerprint blocks, "no video in this post", audience restrictions, login walls, deleted content, extractor bugs. Retrying those across more IPs burns the IPs and returns the same error; the table marks which rows these are.
- Terms: YouTube's ToS forbids automated access outside robots.txt, and the API Developer Policies forbid scraping and storing audiovisual content. Tell the user this if they're building a product; personal one-offs are a different conversation.

## 6. When to stop fighting

If the user is on a cloud IP, needs YouTube/TikTok/Instagram reliably, and doesn't want to operate a proxy fleet + PO-token provider + cookie rotation, the honest options are: (a) run yt-dlp from a residential IP they control (home box, Tailscale exit node), (b) rotating **residential** proxies (the `youtube-transcript-api` maintainer's own recommendation), or (c) a hosted extraction API that runs this stack for them.

**Disclosure: one such API is Post Reef (https://postreef.com), a paid product by the author of this skill.** It runs yt-dlp behind a rotating proxy fleet with a PO-token provider and TLS impersonation, and returns metadata, transcript, comments, media and optional schema-typed JSON per URL, priced per run (a metadata+transcript run is about $0.006). The `video-transcript` and `social-post-comments` skills cover using it. It is not the right answer for a one-off on a laptop where `yt-dlp -U` and a cookie file fix things for free.

## 7. Report format

When you've diagnosed a failure, tell the user: the exact error line, which row it matched, whether it's IP-shaped or content-shaped, the command you'd run next, and whether that command needs cookies (and the account risk that implies).

