{"id":16,"date":"2026-11-04T03:01:40","date_gmt":"2026-11-04T03:01:40","guid":{"rendered":"https:\/\/globalcorpora.com\/blog\/yt-dlp-proxy-guide\/"},"modified":"2026-11-04T03:01:40","modified_gmt":"2026-11-04T03:01:40","slug":"yt-dlp-proxy-guide","status":"publish","type":"post","link":"https:\/\/globalcorpora.com\/blog\/yt-dlp-proxy-guide\/","title":{"rendered":"yt-dlp Proxy Guide: --proxy, SOCKS5, Sessions and Workers"},"content":{"rendered":"<p>Pointing yt-dlp at a proxy takes one flag. Getting it to work reliably across thousands of videos and dozens of workers takes more. This guide covers the proxy URL format, SOCKS vs HTTP, environment variables, config files, the Python API, sticky sessions, aria2c, and a troubleshooting table for the errors you'll actually hit.<\/p>\n<p>Everything below was checked against the <a href=\"https:\/\/github.com\/yt-dlp\/yt-dlp\">yt-dlp README<\/a> and source for release <code>2026.10.26<\/code>, the current stable release as of November 2026. yt-dlp changes quickly, so run <code>yt-dlp -U<\/code> and check <code>yt-dlp --help<\/code> if a flag behaves differently for you.<\/p>\n<h2>TL;DR<\/h2>\n<ul>\n<li>Use <code>--proxy \"http:\/\/USER:PASS@proxy.example.com:8080\"<\/code> or <code>--proxy \"socks5h:\/\/USER:PASS@proxy.example.com:1080\"<\/code>. If the password has special characters, URL-encode it.<\/li>\n<li>In yt-dlp, <code>socks5:\/\/<\/code> is silently rewritten to <code>socks5h:\/\/<\/code>, so DNS is resolved by the proxy either way. That's not true of every external downloader.<\/li>\n<li>If <code>--proxy<\/code> is not set, yt-dlp reads <code>HTTP_PROXY<\/code>\/<code>HTTPS_PROXY<\/code>\/<code>ALL_PROXY<\/code>\/<code>NO_PROXY<\/code> from the environment. <code>--proxy \"\"<\/code> forces a direct connection.<\/li>\n<li>Fetch a video's metadata and download its media from the same exit IP. Use sticky sessions, or one static IP per worker, rather than a new IP on every request.<\/li>\n<li><code>--geo-verification-proxy<\/code> does nothing for YouTube in the current source. Only <code>--proxy<\/code> matters.<\/li>\n<li>aria2c only supports HTTP proxies, and ffmpeg can't use SOCKS proxies.<\/li>\n<\/ul>\n<h2>HTTP\/HTTPS vs SOCKS5, and socks5 vs socks5h<\/h2>\n<p>The <a href=\"https:\/\/github.com\/yt-dlp\/yt-dlp#network-options\">network options<\/a> describe <code>--proxy URL<\/code> as \"Use the specified HTTP\/HTTPS\/SOCKS proxy\". The scheme you put in the URL decides the proxy type:<\/p>\n<table>\n<thead>\n<tr>\n<th>Scheme<\/th>\n<th>What it means<\/th>\n<th>Notes<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>http:\/\/<\/code><\/td>\n<td>Plain HTTP proxy. HTTPS traffic is tunnelled through it with <code>CONNECT<\/code><\/td>\n<td>The most widely supported option. A URL with no scheme is treated as <code>http:\/\/<\/code>.<\/td>\n<\/tr>\n<tr>\n<td><code>https:\/\/<\/code><\/td>\n<td>TLS connection to the proxy itself<\/td>\n<td>Needs the <code>requests<\/code> or <code>curl_cffi<\/code> request handler. The plain <code>urllib<\/code> handler doesn't support it. The README lists <code>requests<\/code> as the dependency \"for HTTPS proxy\". Standalone binaries include it.<\/td>\n<\/tr>\n<tr>\n<td><code>socks5:\/\/<\/code><\/td>\n<td>SOCKS5<\/td>\n<td>yt-dlp rewrites it to <code>socks5h<\/code> (see below).<\/td>\n<\/tr>\n<tr>\n<td><code>socks5h:\/\/<\/code><\/td>\n<td>SOCKS5 with remote DNS<\/td>\n<td>The proxy resolves hostnames.<\/td>\n<\/tr>\n<tr>\n<td><code>socks4:\/\/<\/code>, <code>socks4a:\/\/<\/code><\/td>\n<td>SOCKS4 \/ 4a<\/td>\n<td>No username\/password support in SOCKS4. Rarely useful today.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Most tools use the <code>h<\/code> suffix to mean \"let the proxy resolve DNS\". Without it, the client resolves hostnames locally, which leaks DNS lookups and can pick a CDN node near your server instead of the proxy. yt-dlp handles this differently: in <a href=\"https:\/\/github.com\/yt-dlp\/yt-dlp\/blob\/master\/yt_dlp\/utils\/networking.py\"><code>yt_dlp\/utils\/networking.py<\/code><\/a>, <code>clean_proxies()<\/code> maps <code>'socks5': 'socks5h'<\/code> with the comment \"compat: socks5 was treated as socks5h\". As a result, both schemes give you remote DNS for yt-dlp's own requests.<\/p>\n<p>Still write <code>socks5h:\/\/<\/code> explicitly. It documents your intent, and the same URL may later be passed to curl or another tool where <code>socks5:\/\/<\/code> really does mean local DNS.<\/p>\n<p>HTTP or SOCKS5? For yt-dlp alone, either works. Choose HTTP if you plan to use aria2c or ffmpeg as the downloader (see below). Choose SOCKS5 if your provider only offers SOCKS or you're tunnelling through something like <code>ssh -D<\/code>.<\/p>\n<h2>The --proxy flag and URL format<\/h2>\n<p>The general form is <code>scheme:\/\/user:password@host:port<\/code>:<\/p>\n<pre><code># HTTP proxy with credentials\nyt-dlp --proxy \"http:\/\/USER:PASS@proxy.example.com:8080\" \"https:\/\/www.youtube.com\/watch?v=VIDEO_ID\"\n\n# SOCKS5 with remote DNS\nyt-dlp --proxy \"socks5h:\/\/USER:PASS@proxy.example.com:1080\" \"https:\/\/www.youtube.com\/watch?v=VIDEO_ID\"\n\n# Force a direct connection (overrides env vars and config files)\nyt-dlp --proxy \"\" \"https:\/\/www.youtube.com\/watch?v=VIDEO_ID\"<\/code><\/pre>\n<h3>URL-encode special characters in passwords<\/h3>\n<p>Characters like <code>@ : \/ # ? %<\/code> in a username or password break URL parsing. You'll usually get a confusing connection error or a 407 instead of a clear message. Percent-encode the credentials. yt-dlp and its HTTP backends decode them before sending authentication:<\/p>\n<pre><code>python3 -c 'import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1], safe=\"\"))' 'p@ss:w\/rd#1'\n# p%40ss%3Aw%2Frd%231\n\nyt-dlp --proxy \"http:\/\/USER:p%40ss%3Aw%2Frd%231@proxy.example.com:8080\" URL<\/code><\/pre>\n<p>Always quote the proxy URL in the shell. A <code>#<\/code>, <code>&amp;<\/code> or <code>!<\/code> left unquoted will be read by the shell, not passed to yt-dlp.<\/p>\n<h2>--geo-verification-proxy<\/h2>\n<p>The README says this option makes yt-dlp \"use this proxy to verify the IP address for some geo-restricted sites\", while <code>--proxy<\/code> is still used for the actual download. Only extractors that call the <code>geo_verification_headers()<\/code> helper use it. In <code>2026.10.26<\/code>, the YouTube extractor doesn't. <strong>For YouTube, <code>--geo-verification-proxy<\/code> has no effect.<\/strong> If you need a YouTube video to appear to come from a given country, the exit IP from <code>--proxy<\/code> is what counts.<\/p>\n<h2>Environment variables<\/h2>\n<p>When <code>--proxy<\/code> is absent, yt-dlp calls Python's <code>urllib.request.getproxies()<\/code> (see the <code>proxies<\/code> property in <a href=\"https:\/\/github.com\/yt-dlp\/yt-dlp\/blob\/master\/yt_dlp\/YoutubeDL.py\"><code>YoutubeDL.py<\/code><\/a>). In practice:<\/p>\n<ul>\n<li><code>http_proxy<\/code>\/<code>HTTP_PROXY<\/code> and <code>https_proxy<\/code>\/<code>HTTPS_PROXY<\/code> are used for those URL schemes. If only the HTTP one is set, yt-dlp reuses it for HTTPS. Set <code>HTTPS_PROXY=__noproxy__<\/code> to undo that.<\/li>\n<li><code>all_proxy<\/code>\/<code>ALL_PROXY<\/code> is the fallback for any scheme.<\/li>\n<li><code>no_proxy<\/code>\/<code>NO_PROXY<\/code> is honoured.<\/li>\n<li>On macOS and Windows, if no proxy environment variables are set, <code>getproxies()<\/code> can fall back to the system proxy settings. A desktop VPN or proxy app can therefore change yt-dlp's routing without you noticing.<\/li>\n<li>Any <code>--proxy<\/code> value replaces the environment completely, including <code>--proxy \"\"<\/code>.<\/li>\n<\/ul>\n<pre><code>export HTTPS_PROXY=\"http:\/\/USER:PASS@proxy.example.com:8080\"\nexport HTTP_PROXY=\"$HTTPS_PROXY\"\nyt-dlp URL<\/code><\/pre>\n<h2>Config files<\/h2>\n<p>Any CLI option can go into a <a href=\"https:\/\/github.com\/yt-dlp\/yt-dlp#configuration\">config file<\/a>. yt-dlp searches, in order: the file given to <code>--config-locations<\/code>, a portable <code>yt-dlp.conf<\/code> next to the binary, <code>yt-dlp.conf<\/code> in the <code>-P<\/code> home path (or the current directory), the user config, and the system config. Recommended user locations are <code>${XDG_CONFIG_HOME}\/yt-dlp\/config<\/code> on Linux\/macOS and <code>${APPDATA}\/yt-dlp\/config<\/code> on Windows. <code>~\/yt-dlp.conf<\/code>, <code>~\/.yt-dlp\/config<\/code> and <code>\/etc\/yt-dlp.conf<\/code> also work.<\/p>\n<pre><code># ~\/.config\/yt-dlp\/config\n--proxy \"http:\/\/USER:PASS@proxy.example.com:8080\"\n--socket-timeout 30\n--retries 10\n--fragment-retries 10<\/code><\/pre>\n<p>Two gotchas. Config lines are parsed like a shell, so quote values containing <code>#<\/code>. And <code>--ignore-config<\/code> on the command line skips all of these files. That helps when you're debugging which proxy is actually in effect. <code>yt-dlp -v<\/code> prints which config files were loaded.<\/p>\n<h2>Using a proxy from the Python API<\/h2>\n<p>The option keys match the CLI flags: <code>proxy<\/code>, <code>socket_timeout<\/code>, <code>retries<\/code>, <code>fragment_retries<\/code>, <code>source_address<\/code>. One difference matters a lot: according to the docstrings, <code>retries<\/code> and <code>fragment_retries<\/code> <strong>default to 0 in the API but 10 in the CLI<\/strong>. Set them explicitly.<\/p>\n<pre><code>import yt_dlp\n\nopts = {\n    \"proxy\": \"http:\/\/USER:PASS@proxy.example.com:8080\",\n    \"socket_timeout\": 30,\n    \"retries\": 10,\n    \"fragment_retries\": 10,\n    \"outtmpl\": \"data\/%(id)s.%(ext)s\",\n}\n\nwith yt_dlp.YoutubeDL(opts) as ydl:\n    info = ydl.extract_info(\"https:\/\/www.youtube.com\/watch?v=VIDEO_ID\", download=True)\n    print(info[\"id\"], info.get(\"title\"))<\/code><\/pre>\n<h2>Check your exit IP<\/h2>\n<p>Before blaming YouTube, confirm the proxy works and see which IP you're actually exiting from. With curl:<\/p>\n<pre><code>curl -x \"http:\/\/USER:PASS@proxy.example.com:8080\" https:\/\/api.ipify.org\ncurl -x \"socks5h:\/\/USER:PASS@proxy.example.com:1080\" https:\/\/api.ipify.org<\/code><\/pre>\n<p>A better check goes through yt-dlp's own networking stack, so the proxy parsing, handlers and environment handling are the same ones your downloads use:<\/p>\n<pre><code>import yt_dlp\n\nwith yt_dlp.YoutubeDL({\"proxy\": \"http:\/\/USER:PASS@proxy.example.com:8080\"}) as ydl:\n    print(ydl.urlopen(\"https:\/\/api.ipify.org\").read().decode())<\/code><\/pre>\n<p>Run it twice in a row. If the IP changes between calls, your endpoint rotates per request or per connection. That matters for the next section.<\/p>\n<h2>Sticky vs rotating sessions: keep one video on one IP<\/h2>\n<p>Downloading a YouTube video takes several requests: the watch page and player API calls (metadata, format list, signed media URLs), then many range requests to <code>googlevideo.com<\/code> for the media. The <a href=\"https:\/\/github.com\/yt-dlp\/yt-dlp\/wiki\/FAQ\">yt-dlp FAQ<\/a> puts it this way: \"In many cases, requests for the video (to download\/play it) must come from the same IP address and with the same cookies and\/or HTTP headers.\" Google doesn't document YouTube's signed media URLs, so the exact binding rules are an observed behaviour rather than a contract. The practical rule is simple, though: <strong>if extraction happens on IP A and the media download on IP B, expect intermittent 403s.<\/strong> The <a href=\"https:\/\/github.com\/yt-dlp\/yt-dlp\/wiki\/PO-Token-Guide\">PO Token Guide<\/a> adds another binding: most PO tokens are tied to a session or a video ID, which is another reason to keep each video's requests together.<\/p>\n<p>Proxies come in two broad modes:<\/p>\n<ul>\n<li><strong>Rotating per request or connection<\/strong>: every new TCP connection may exit from a different IP. That works for stateless page fetches and is risky for yt-dlp, because one download opens several connections.<\/li>\n<li><strong>Sticky sessions<\/strong>: the provider pins you to one exit IP for a set time, usually selected by a session ID in the proxy username. The exact syntax is provider-specific, so check your provider's docs. Static or dedicated IPs behave like a permanently sticky session.<\/li>\n<\/ul>\n<p>The pattern that works: <strong>one sticky session per video<\/strong> (or per worker, for a batch of videos), long enough to cover extraction and the full media download. Then move on to a new session. Rotation still spreads load across the pool; it just happens between videos, not inside one.<\/p>\n<h2>Running many workers, each with its own proxy session<\/h2>\n<p>Run a separate <code>YoutubeDL<\/code> instance per job, in separate processes, and derive a session ID from the video so retries of the same video get the same IP. The username format below is <em>illustrative<\/em>; substitute your provider's sticky-session syntax.<\/p>\n<pre><code>import hashlib\nimport urllib.parse\nfrom concurrent.futures import ProcessPoolExecutor\n\nimport yt_dlp\n\nPROXY_USER = \"USER\"\nPROXY_PASS = urllib.parse.quote(\"PASS\", safe=\"\")\nPROXY_HOST = \"proxy.example.com:8080\"\n\n\ndef proxy_for(url: str) -&gt; str:\n    session = hashlib.sha1(url.encode()).hexdigest()[:12]\n    # Sticky-session username syntax varies by provider; this format is illustrative.\n    return f\"http:\/\/{PROXY_USER}-session-{session}:{PROXY_PASS}@{PROXY_HOST}\"\n\n\ndef fetch(url: str) -&gt; tuple[str, int]:\n    opts = {\n        \"proxy\": proxy_for(url),\n        \"socket_timeout\": 30,\n        \"retries\": 5,\n        \"fragment_retries\": 5,\n        \"outtmpl\": \"data\/%(id)s.%(ext)s\",\n        \"download_archive\": \"archive.txt\",\n        \"quiet\": True,\n        \"noprogress\": True,\n    }\n    try:\n        with yt_dlp.YoutubeDL(opts) as ydl:\n            return url, ydl.download([url])\n    except yt_dlp.utils.DownloadError:\n        return url, 1\n\n\nif __name__ == \"__main__\":\n    with open(\"urls.txt\") as f:\n        urls = [line.strip() for line in f if line.strip()]\n    with ProcessPoolExecutor(max_workers=8) as pool:\n        for url, code in pool.map(fetch, urls):\n            print(code, url)<\/code><\/pre>\n<p>With a list of static proxies instead, assign one proxy per worker. From the shell, a file with one <code>URL PROXY<\/code> pair per line plus <code>xargs<\/code> does the same job:<\/p>\n<pre><code>xargs -P 8 -L 1 sh -c 'yt-dlp --proxy \"$1\" -o \"data\/%(id)s.%(ext)s\" -- \"$0\"' &lt; jobs.txt<\/code><\/pre>\n<p>The shared <code>archive.txt<\/code> above is fine for a single machine. Across machines, use a real job queue. Concurrency per IP matters as much as the total: many parallel videos behind one exit IP is how IPs get flagged. Pacing and IP reputation are covered in <a href=\"https:\/\/globalcorpora.com\/blog\/yt-dlp-sign-in-to-confirm-not-a-bot\/\">fixing \"Sign in to confirm you're not a bot\"<\/a>, and sizing the total pipe in <a href=\"https:\/\/globalcorpora.com\/blog\/bandwidth-for-large-scale-video-downloads\/\">bandwidth for large-scale video downloads<\/a>.<\/p>\n<h2>aria2c as the external downloader<\/h2>\n<p><code>--downloader aria2c<\/code> hands HTTP\/FTP downloads to <a href=\"https:\/\/aria2.github.io\/manual\/en\/html\/aria2c.html\">aria2c<\/a>. In <a href=\"https:\/\/github.com\/yt-dlp\/yt-dlp\/blob\/master\/yt_dlp\/downloader\/external.py\"><code>downloader\/external.py<\/code><\/a>, yt-dlp passes your <code>--proxy<\/code> value to aria2c as <code>--all-proxy<\/code>. It also sets <code>-x16 -s16 --min-split-size 1M<\/code> by default, then appends whatever you give <code>--downloader-args<\/code>.<\/p>\n<pre><code>yt-dlp --proxy \"http:\/\/USER:PASS@proxy.example.com:8080\" \\\n  --downloader aria2c \\\n  --downloader-args \"aria2c:-x 4 -s 4 --connect-timeout=15 --timeout=30 --max-tries=5\" \\\n  \"https:\/\/www.youtube.com\/watch?v=VIDEO_ID\"<\/code><\/pre>\n<p>Three caveats:<\/p>\n<ul>\n<li><strong>HTTP proxies only.<\/strong> The aria2 manual gives the proxy format as <code>[http:\/\/][USER:PASSWORD@]HOST[:PORT]<\/code>. aria2c doesn't speak SOCKS. With a SOCKS proxy, use the native downloader. (ffmpeg has the same limitation: yt-dlp warns that it \"does not support SOCKS proxies\".)<\/li>\n<li><strong>16 connections per file.<\/strong> On a per-connection rotating proxy, each segment may come from a different IP, which brings back the 403 problem. Use a sticky session and fewer connections (<code>-x 4<\/code>), or skip aria2c.<\/li>\n<li>aria2c reads proxy environment variables itself. Only an explicit <code>--proxy<\/code> is forwarded by yt-dlp, so test the exact configuration you deploy.<\/li>\n<\/ul>\n<p>Add <code>-v<\/code> to see the final command line (<code>[debug] aria2c command line: ...<\/code>) and confirm which arguments took effect.<\/p>\n<h2>Timeouts and retries<\/h2>\n<table>\n<thead>\n<tr>\n<th>Flag (API key)<\/th>\n<th>Default<\/th>\n<th>What it does<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>--socket-timeout<\/code> (<code>socket_timeout<\/code>)<\/td>\n<td>20 s (<code>DEFAULT_TIMEOUT<\/code> in the networking layer)<\/td>\n<td>How long to wait on an unresponsive connection. Raise it to 30\u201360 s for slow residential exits.<\/td>\n<\/tr>\n<tr>\n<td><code>-R, --retries<\/code> (<code>retries<\/code>)<\/td>\n<td>10 CLI \/ 0 API<\/td>\n<td>Retries for the download on expected network errors. Accepts <code>infinite<\/code>.<\/td>\n<\/tr>\n<tr>\n<td><code>--fragment-retries<\/code> (<code>fragment_retries<\/code>)<\/td>\n<td>10 CLI \/ 0 API<\/td>\n<td>Retries per fragment for DASH, hlsnative and ISM.<\/td>\n<\/tr>\n<tr>\n<td><code>--retry-sleep [TYPE:]EXPR<\/code><\/td>\n<td>none<\/td>\n<td>Backoff between retries, e.g. <code>--retry-sleep exp=1:30<\/code> or <code>--retry-sleep fragment:linear=1::2<\/code>.<\/td>\n<\/tr>\n<tr>\n<td><code>--extractor-retries<\/code><\/td>\n<td>3<\/td>\n<td>Retries for known extractor errors.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Retries go through the same proxy URL. If that URL is a sticky session, they reuse the same IP, which is what you want for a transient error. For a hard block (repeated 403 or 429), retrying on the same IP rarely helps. Fail fast, record the video, and requeue it on a fresh session.<\/p>\n<h2>Choosing a proxy type for this workload<\/h2>\n<p>Video collection is unusual among proxy workloads: few requests, lots of bytes. That changes the trade-offs.<\/p>\n<table>\n<thead>\n<tr>\n<th>Type<\/th>\n<th>Strengths<\/th>\n<th>Weaknesses for yt-dlp<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Datacenter (shared)<\/td>\n<td>Cheap, fast, stable IPs<\/td>\n<td>Cloud and hosting ranges are the most likely to get bot checks and 403s from YouTube. Shared IPs inherit other users' reputation.<\/td>\n<\/tr>\n<tr>\n<td>Residential (rotating)<\/td>\n<td>IPs look like home users, many locations<\/td>\n<td>Usually billed per GB, which gets expensive at video volumes. Bandwidth and latency vary by peer. Sessions can drop mid-download.<\/td>\n<\/tr>\n<tr>\n<td>ISP \/ static residential<\/td>\n<td>Residential-registered IPs on datacenter hardware. Stable, so naturally sticky<\/td>\n<td>Fewer IPs per dollar. A flagged IP stays flagged until replaced.<\/td>\n<\/tr>\n<tr>\n<td>High-bandwidth dedicated (unmetered, per-Gbps)<\/td>\n<td>Predictable cost for terabyte-scale transfer, consistent throughput, capacity reserved for you<\/td>\n<td>You pay for the capacity whether you use it or not. IP reputation still depends on the ranges and on how hard you push each IP.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The test that matters is your own: run a few hundred videos through each candidate, then compare 403\/bot-check rates and cost per delivered GB.<\/p>\n<h2>Troubleshooting<\/h2>\n<table>\n<thead>\n<tr>\n<th>Symptom<\/th>\n<th>Likely cause<\/th>\n<th>Fix<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>407 Proxy Authentication Required<\/code><\/td>\n<td>Wrong credentials, unencoded special characters in the password, or source-IP allow-listing on the provider side<\/td>\n<td>URL-encode the credentials. Test with <code>curl -x<\/code>. Check the provider's allow-list.<\/td>\n<\/tr>\n<tr>\n<td><code>Unable to connect to proxy<\/code> \/ <code>Connection refused<\/code><\/td>\n<td>Wrong host or port, wrong scheme (SOCKS port with <code>http:\/\/<\/code>), or a firewall<\/td>\n<td>Match the scheme to the port the provider documents. Check with <code>curl -x<\/code>.<\/td>\n<\/tr>\n<tr>\n<td><code>SSL: CERTIFICATE_VERIFY_FAILED<\/code> or <code>WRONG_VERSION_NUMBER<\/code><\/td>\n<td>Using <code>https:\/\/<\/code> for a plain HTTP proxy (or the reverse), or an intercepting proxy<\/td>\n<td>Usually you want <code>http:\/\/<\/code>. <code>--no-check-certificates<\/code> hides the problem instead of fixing it; only use it for debugging.<\/td>\n<\/tr>\n<tr>\n<td>Timeouts, <code>Read timed out<\/code><\/td>\n<td>Slow or overloaded exit, peer dropped<\/td>\n<td>Raise <code>--socket-timeout<\/code>, add <code>--retry-sleep<\/code>, reduce concurrency per IP.<\/td>\n<\/tr>\n<tr>\n<td><code>HTTP Error 403<\/code> on media after extraction succeeded<\/td>\n<td>Extraction and download from different IPs (rotating proxy, aria2c multi-connection), missing PO token, outdated yt-dlp<\/td>\n<td>Sticky session per video, <code>yt-dlp -U<\/code>, see the <a href=\"https:\/\/github.com\/yt-dlp\/yt-dlp\/wiki\/PO-Token-Guide\">PO Token Guide<\/a>.<\/td>\n<\/tr>\n<tr>\n<td>\"Sign in to confirm you're not a bot\"<\/td>\n<td>IP reputation or request rate<\/td>\n<td>See <a href=\"https:\/\/globalcorpora.com\/blog\/yt-dlp-sign-in-to-confirm-not-a-bot\/\">our dedicated guide<\/a>.<\/td>\n<\/tr>\n<tr>\n<td>Proxy seems ignored<\/td>\n<td><code>--proxy<\/code> in a config file, a system or desktop proxy, or <code>NO_PROXY<\/code> matching<\/td>\n<td><code>yt-dlp -v --ignore-config<\/code>, then check the exit IP through <code>ydl.urlopen<\/code>.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2>A note on compliance<\/h2>\n<p>YouTube's Terms of Service restrict automated access and downloading, and a proxy doesn't change that. If you're collecting video for training data, copyright and licensing also matter (for example, filtering for Creative Commons\u2013licensed content), as do privacy obligations for faces and voices (GDPR and similar laws) and a process for honouring takedown and opt-out requests. Get legal review for your jurisdiction and use case.<\/p>\n<h2>If you'd rather not run this yourself<\/h2>\n<p>Corpora offers dedicated 1\u201340 Gbps unmetered proxy capacity (HTTP(S) and SOCKS5, yt-dlp compatible, rotating pools, multi-region) for teams running their own stack. See <a href=\"https:\/\/globalcorpora.com\/#pricing\">pricing<\/a>. If you only need the end result, Corpora can also collect, deduplicate and deliver video, audio, subtitles and metadata to your object storage. <a href=\"https:\/\/globalcorpora.com\/#contact\">Get in touch<\/a> for a free sample batch. For the pipeline side, see <a href=\"https:\/\/globalcorpora.com\/blog\/build-youtube-video-dataset-for-multimodal-training\/\">building a YouTube video dataset for multimodal training<\/a> and <a href=\"https:\/\/globalcorpora.com\/blog\/download-youtube-video-datasets-panda-70m-hd-vila-internvid\/\">downloading Panda-70M, HD-VILA and InternVid<\/a>.<\/p>\n<h2>FAQ<\/h2>\n<h3>How do I use a SOCKS5 proxy with yt-dlp?<\/h3>\n<p>Pass <code>--proxy \"socks5h:\/\/USER:PASS@proxy.example.com:1080\"<\/code>. yt-dlp treats <code>socks5:\/\/<\/code> as <code>socks5h:\/\/<\/code>, so DNS goes through the proxy either way. Don't combine SOCKS with <code>--downloader aria2c<\/code> or ffmpeg, which don't support SOCKS proxies.<\/p>\n<h3>Does yt-dlp use the HTTP_PROXY environment variable?<\/h3>\n<p>Yes, when <code>--proxy<\/code> isn't given. It reads <code>HTTP_PROXY<\/code>, <code>HTTPS_PROXY<\/code>, <code>ALL_PROXY<\/code> and <code>NO_PROXY<\/code> (upper or lower case) through Python's <code>urllib<\/code>. Any <code>--proxy<\/code> value, including <code>--proxy \"\"<\/code>, overrides them.<\/p>\n<h3>Why do I get 403 Forbidden when downloading through a rotating proxy?<\/h3>\n<p>Usually because the media requests left from a different IP than the one that fetched the video's metadata and signed URLs. Use a sticky session for the whole video. Also update yt-dlp and check PO-token requirements.<\/p>\n<h3>Can I use a different proxy for each video in yt-dlp?<\/h3>\n<p>Not within a single CLI invocation. <code>--proxy<\/code> applies to the whole run. Launch one process (or one <code>YoutubeDL<\/code> instance) per video or per worker, each with its own proxy URL, as in the worker example above.<\/p>\n<h3>How do I put a password with special characters in the yt-dlp proxy URL?<\/h3>\n<p>Percent-encode it, e.g. with <code>urllib.parse.quote(password, safe=\"\")<\/code>, so that <code>@<\/code> becomes <code>%40<\/code> and <code>:<\/code> becomes <code>%3A<\/code>. Then quote the whole URL in the shell.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>How to use a yt-dlp proxy: --proxy URL format, socks5 vs socks5h, env vars, config files, Python API, sticky sessions, aria2c and fixing 403\/407 errors.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[16,17,9,15,7],"class_list":["post-16","post","type-post","status-publish","format-standard","hentry","category-guides","tag-aria2c","tag-data-collection","tag-proxies","tag-socks5","tag-yt-dlp"],"_links":{"self":[{"href":"https:\/\/globalcorpora.com\/blog\/wp-json\/wp\/v2\/posts\/16","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/globalcorpora.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/globalcorpora.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/globalcorpora.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/globalcorpora.com\/blog\/wp-json\/wp\/v2\/comments?post=16"}],"version-history":[{"count":0,"href":"https:\/\/globalcorpora.com\/blog\/wp-json\/wp\/v2\/posts\/16\/revisions"}],"wp:attachment":[{"href":"https:\/\/globalcorpora.com\/blog\/wp-json\/wp\/v2\/media?parent=16"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/globalcorpora.com\/blog\/wp-json\/wp\/v2\/categories?post=16"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/globalcorpora.com\/blog\/wp-json\/wp\/v2\/tags?post=16"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}