Tag: troubleshooting

  • yt-dlp "Sign in to confirm you're not a bot": How to Fix It

    If yt-dlp suddenly fails with ERROR: [youtube] VIDEO_ID: Sign in to confirm you're not a bot, YouTube has decided that your session or your IP address looks automated. It often shows up together with HTTP 403, HTTP 429 or "This content isn't available, try again later". This guide walks through the fixes in order, cheapest first. It keeps two cases apart: one person on a laptop, and a pipeline doing thousands of downloads per hour from servers.

    Everything below was checked against the yt-dlp README, the official wiki and the source code as of early November 2026, when the latest stable release was 2026.10.26. YouTube changes enforcement often, so treat client names and limits as a snapshot.

    TL;DR

    • Update first. Run yt-dlp -U, or switch to the nightly channel with yt-dlp --update-to nightly. Many "bot" and 403 reports turn out to be outdated installs.
    • Install a JavaScript runtime. YouTube extraction now needs an external JS runtime (Deno is the default) plus the yt-dlp-ejs scripts. Without them, some formats go missing.
    • Slow down. The wiki puts the guest rate limit at about 300 videos/hour per session. -t sleep applies sensible delays for you.
    • PO tokens fix 403s on some clients, not the bot check. Use a PO Token Provider plugin such as bgutil-ytdlp-pot-provider.
    • Cookies work, but they put the account at risk. Fine for one person on a laptop. At pipeline scale, expect accounts to be banned.
    • On servers, IP reputation decides most of the outcome. Datacenter ranges get the bot check much more often than residential IPs. Spread load across IPs and sessions instead of pushing harder through one.

    What the error actually means

    The text comes from YouTube's player response, not from yt-dlp. yt-dlp appends a hint ("Use --cookies-from-browser or --cookies for the authentication") and links the wiki's cookie instructions. Don't misread it: the video doesn't need an account. YouTube wants a session it trusts more than an anonymous request from your IP.

    The related errors have different causes, which is why they need different fixes:

    • "This content isn't available, try again later" means your session hit the rate limit. In current yt-dlp versions, the error says the session "has been rate-limited by YouTube for up to an hour" and suggests -t sleep.
    • HTTP Error 403: Forbidden on the actual media download usually means a missing or invalid PO token for the client in use, an outdated yt-dlp, or a JS challenge that failed. Less often, the IP is blocked.
    • HTTP Error 429: Too Many Requests means your IP is being throttled. The yt-dlp FAQ calls this "usually a soft block".

    Error → cause → fix

    Error message Likely cause First fix to try
    Sign in to confirm you're not a bot Low-reputation IP (often datacenter), or too many requests from one IP or session Update, slow down, and change egress IP. Use cookies only as a last resort (account risk).
    This content isn't available, try again later Session rate limit: about 300 videos/hour for guests, about 2,000/hour for accounts (per the wiki) -t sleep or the sleep flags. Wait up to an hour.
    HTTP Error 403: Forbidden on media/fragments Missing PO token (GVS) for the chosen client, outdated yt-dlp, or a JS challenge that wasn't solved Update, install Deno and EJS, add a PO Token Provider plugin
    HTTP Error 429: Too Many Requests IP throttled for overuse Back off. Use fewer parallel workers per IP. Move to another IP.
    No supported JavaScript runtime could be found (warning) No Deno, or another runtime not enabled Install Deno, or pass --js-runtimes node
    n challenge solving failed: Some formats may be missing EJS scripts missing or outdated, or the runtime is too old Update yt-dlp together with yt-dlp-ejs. Check the runtime version.
    YouTube is requiring a captcha challenge before playback IP or session flagged Stop and rotate the IP. Don't retry in a tight loop.

    Step 1: Diagnose with -v

    Before you change anything, run a single failing URL with verbose output:

    yt-dlp -v "https://www.youtube.com/watch?v=VIDEO_ID"

    Check the [debug] header for the version and channel, the JS runtimes: line, and the detected JS Challenge Providers and PO Token Providers. If the runtimes line says none, or a provider you installed is missing, you've found the problem. Add --print-traffic for the raw HTTP exchange.

    Step 2: Update yt-dlp (and pick the right channel)

    YouTube breaks yt-dlp regularly, and fixes ship fast. The README's update section describes three channels. stable is released on a roughly monthly schedule and is "often 'stale' and prone to external breakage". nightly is the "recommended channel for regular users". master publishes after every push.

    # Official binary: update within the current channel
    yt-dlp -U
    
    # Switch a stable binary to nightly
    yt-dlp --update-to nightly
    
    # pip install: re-run the install, including the default extras (pulls in yt-dlp-ejs)
    python3 -m pip install -U "yt-dlp[default]"
    
    # pip nightly (development releases)
    python3 -m pip install -U --pre "yt-dlp[default]"

    If your distro package is outdated, the FAQ says to install the official release instead. In a pipeline, pin a version in your image but rebuild it often.

    Step 3: Install a JavaScript runtime and the EJS scripts

    This is the newest requirement and a common cause of missing formats and 403s. Per the EJS wiki page, yt-dlp now solves YouTube's JavaScript challenges with an external runtime that runs the yt-dlp-ejs solver scripts. The old built-in interpreter is no longer used for YouTube, and the code now calls extraction without a JS runtime "deprecated".

    • Deno (2.3.0 or newer) is recommended and enabled by default.
    • Node (22.0.0 or newer) works with --js-runtimes node. QuickJS works with --js-runtimes quickjs. Bun support is deprecated.
    • The official binaries (yt-dlp_linux, yt-dlp.exe, the zipimport yt-dlp) bundle yt-dlp-ejs. If you install with pip, use yt-dlp[default]. If your distro package doesn't ship the scripts, add --remote-components ejs:github (or ejs:npm with Deno or Bun).
    # Deno somewhere other than PATH
    yt-dlp --js-runtimes "deno:/opt/deno/bin/deno" URL
    
    # Use Node instead of Deno (clear the defaults first if Deno is also installed)
    yt-dlp --no-js-runtimes --js-runtimes node URL

    The wiki stresses that yt-dlp-ejs must be updated together with yt-dlp, because older versions are ignored. In Docker images, install both in the same layer.

    Step 4: Slow down

    The Extractors wiki gives concrete numbers. With default settings, the limit is "~300 videos/hour (~1000 webpage/player requests per hour)" for guest sessions and "~2000 videos/hour (~4000 webpage/player requests per hour)" for accounts. It recommends a delay of about 5–10 seconds between downloads.

    The easiest option is the built-in preset. -t sleep expands to --sleep-subtitles 5 --sleep-requests 0.75 --sleep-interval 10 --max-sleep-interval 20. The individual flags:

    Flag What it does
    --sleep-requests SECONDS Sleep between requests during extraction (webpage, player API calls)
    --sleep-interval SECONDS (alias --min-sleep-interval) Sleep before each download. Acts as the minimum when combined with the max.
    --max-sleep-interval SECONDS Upper bound for a random sleep. Only valid together with the minimum.
    --sleep-subtitles SECONDS Sleep before each subtitle download
    -r, --limit-rate RATE Cap download bandwidth, e.g. 5M. Makes traffic look less bursty but doesn't change the request count.
    -R, --retries / --retry-sleep Retry count (default 10) and backoff, e.g. --retry-sleep http:exp=1:120

    Throughput math: with -t sleep the per-video sleep averages (10 + 20) / 2 = 15 s, so one worker starts at most 3600 / 15 = 240 videos/hour, under the ~300/hour guest figure. Faster pipelines need more sessions, not less sleep.

    Step 5: PO tokens and player clients (for 403s)

    The PO Token Guide describes a Proof of Origin token that some YouTube clients must send. Without it, "requests for the affected clients' format URLs may return HTTP Error 403, or result in your account or IP address being blocked". Tokens are now bound to the video ID, so pasting a token by hand is no longer recommended. The guide's recommended setup is a PO Token Provider plugin that feeds the mweb client. It features two plugins: bgutil-ytdlp-pot-provider and yt-dlp-getpot-wpc.

    # 1) Run the bgutil token server, bound to loopback only
    docker run --name bgutil-provider -d --init \
      -p 127.0.0.1:4416:4416 brainicism/bgutil-ytdlp-pot-provider
    
    # 2) Install the plugin next to a pip-installed yt-dlp
    python3 -m pip install -U bgutil-ytdlp-pot-provider
    
    # 3) Use it with the mweb client
    yt-dlp -v --extractor-args "youtube:player_client=default,mweb" URL

    In the -v output, look for [pot:bgutil:http] Generating a gvs PO Token. The bgutil README warns that leaving 127.0.0.1 out of the port mapping exposes an unauthenticated server.

    About player_client: in 2026.10.26 the README gives the default as tv_samsung,visionos,web, with a different set when logged-in cookies are passed. Use default,-web syntax to add or exclude clients. The wiki's PO token table predates some of these clients, so check the README for your installed version rather than copying old forum advice.

    An important limit, stated in the bgutil README: a PO token does not bypass IP-based login restrictions. If you still see "Sign in to confirm you're not a bot" after a token was generated, which is common on datacenter IPs, the problem is your IP or session, not the token.

    Step 6: Cookies (laptop yes, pipeline carefully)

    For a single user, logged-in cookies are the quickest way past the bot check. The Extractors wiki has a caution box: "By using your account with yt-dlp, you run the risk of it being banned (temporarily or permanently)." It suggests a throwaway account. YouTube rotates cookies on open tabs, so the wiki's method for long-lived cookies is this: log in from a private window, open https://www.youtube.com/robots.txt in that same tab, export the youtube.com cookies with an extension, then close the window.

    # Laptop: read cookies straight from a browser profile
    yt-dlp --cookies-from-browser firefox URL
    
    # Exported Netscape-format file
    yt-dlp --cookies /secure/path/cookies.txt URL

    Two practical points. First, the wiki says not to export with --cookies-from-browser BROWSER --cookies FILE for this purpose, because that exports your regular browser cookies, not the private session. Second, --cookies FILE both reads and writes the file. If you share one file across parallel workers, they share one session, write over each other's changes, and burn through that account's ~2,000 videos/hour budget together.

    At pipeline scale, don't build on accounts. Accounts get a higher rate limit, but every ban costs you one. Use cookies to retry the occasional login-gated video, not as your main path.

    Step 7: IP reputation, proxies and spreading load

    This is the step laptop guides skip. YouTube judges requests partly by source IP, and cloud and hosting ranges carry far more automated traffic than home connections. They hit the bot check much sooner, so a command that works at home can fail immediately on a fresh VM. Google publishes no thresholds, so test on your own ranges.

    yt-dlp's tools for this are --proxy (HTTP, HTTPS or SOCKS) and --source-address, which binds to a specific local IP if your host has several. Use -4 or -6 to pin the address family.

    # HTTP proxy with auth
    yt-dlp --proxy "http://USER:[email protected]:8080" URL
    
    # SOCKS5 (note the scheme)
    yt-dlp --proxy "socks5://USER:[email protected]:1080" URL

    For a full walkthrough of proxy types, authentication and rotation patterns, see our yt-dlp proxy guide.

    A pattern for thousands of downloads per hour

    1. One worker = one egress IP = one guest session. Give each worker its own proxy endpoint or sticky session and its own cookie jar (or none). Don't share state between workers.
    2. Size the pool with arithmetic, not hope. With -t sleep a worker waits 10–20 s (15 s on average) before each download, so it tops out at about 3,600 ÷ 15 = 240 videos/hour before counting the download time itself. Using that ceiling, a target of 5,000 videos/hour needs at least 5000 / 240 ≈ 21 workers, each on separate IPs. Plan for more, since some IPs will be cooling down at any moment. For bandwidth sizing, see our bandwidth guide.
    3. Treat bot and rate-limit errors as signals, not retryable failures. When an IP returns the bot check or a 429, take it out of rotation for at least an hour. Hammering it with retries extends the block.
    4. Keep the toolchain current everywhere. yt-dlp, yt-dlp-ejs, Deno and the PO token plugin should be rebuilt together on a regular schedule.
    5. Make runs resumable. Use --download-archive so a rotated or restarted worker skips finished IDs.
    # yt-dlp.conf for one pipeline worker
    --proxy http://USER:[email protected]:8080
    --sleep-requests 1
    --sleep-interval 10
    --max-sleep-interval 20
    --sleep-subtitles 5
    --retries 5
    --retry-sleep http:exp=1:120
    --extractor-retries 3
    --download-archive /data/archive.txt
    -o /data/%(id)s.%(ext)s

    For the surrounding pipeline (manifests, deduplication, metadata), see how to build a YouTube video dataset for multimodal training and our notes on downloading Panda-70M, HD-VILA and InternVid.

    Troubleshooting order, cheapest first

    1. Run -v on one URL and read the debug header.
    2. Update yt-dlp (-U or --update-to nightly) and yt-dlp-ejs.
    3. Install Deno (or enable Node/QuickJS) and confirm the JS runtimes: line.
    4. Add -t sleep and cut parallelism per IP.
    5. Install a PO Token Provider plugin. Try player_client variations if 403s persist.
    6. Laptop: pass cookies from a throwaway account. Pipeline: move to cleaner IPs and spread load across more of them.
    7. Still stuck on the latest nightly? Check the pinned known-issues thread and the issue tracker before you file a report with full -v output.

    A note on compliance

    YouTube's Terms of Service restrict automated access and downloading. Building a training set also involves copyright and licensing (for example, filtering for Creative Commons content), privacy rules for faces and voices (such as GDPR), and honoring takedown and opt-out requests. Get legal review for your jurisdiction and use case.

    If you'd rather not run this yourself

    Corpora offers dedicated 1–40 Gbps unmetered proxy capacity (HTTP(S) and SOCKS5, rotating pools, multi-region) that drops into the --proxy setup above, at $2,000 per Gbps per month. If you'd rather skip the infrastructure, we can also collect and deliver video, audio, subtitles and metadata straight to your object storage. Get in touch for a free sample batch.

    FAQ

    Why does yt-dlp say "Sign in to confirm you're not a bot" on my server but not on my laptop?

    The message comes from YouTube and depends heavily on the source IP. Datacenter and cloud IPs are flagged far more readily than residential connections. Updating and slowing down help, but on servers the lasting fix is better IPs plus spreading load across them.

    Does a PO token fix "Sign in to confirm you're not a bot"?

    Usually not. PO tokens fix 403s on clients that need them. Per the bgutil README, a token "does not bypass IP-based login restrictions".

    Do I need Deno for yt-dlp now?

    For YouTube you need a supported JavaScript runtime. Deno is the default. Node and QuickJS work with --js-runtimes.

    Is using cookies with yt-dlp safe?

    The wiki warns the account can be banned temporarily or permanently. Use a throwaway account and low request rates.

    How long does the "This content isn't available, try again later" block last?

    yt-dlp's own message says the session is rate-limited "for up to an hour". Wait, then resume with -t sleep or equivalent delays so you stay under the roughly 300 videos/hour guest limit.