
Last Update: August 19, 2026
BY
eric
Keywords
yt-dlp gave us the least helpful error message it has: HTTP Error 410: Gone. The standard fix is yt-dlp -U. We ran it. Already on the latest stable release. The video was not, in fact, gone — but proving that took three separate bugs stacked on top of each other, and the last one had nothing to do with yt-dlp at all.
We were debugging a small local downloader tool — a browser extension that hands a page URL and cookies to yt-dlp on the user's own machine, optionally routed through a local HTTP proxy for geo-unblocking. One specific site started failing every time, on a URL that worked fine a week earlier.
The first clue was buried in -v
The plain error told us nothing. Adding -v for verbose output surfaced a line that's easy to scroll past:
WARNING: [SiteExtractor] The extractor is attempting impersonation, but no
impersonate target is available. If you encounter errors, then see
https://github.com/yt-dlp/yt-dlp#impersonation for information on
installing the required dependencies
That's the real signal. A growing number of sites fingerprint the TLS handshake and HTTP/2 frame ordering of incoming requests to tell a real browser apart from a scripted client — even when every header looks identical. yt-dlp can spoof a browser's TLS fingerprint via an optional dependency, curl_cffi, which wraps libcurl with impersonation profiles for Chrome, Safari, Firefox and so on. Without it, some extractors still try the request, get rejected at the TLS layer, and the site's edge returns a generic 410 instead of anything that explains what actually happened. The video was fine. yt-dlp just didn't look enough like a browser to be allowed to ask.
The version window nobody tells you about
The fix looked trivial:
python3 -m pip install curl_cffi
It installed cleanly — curl_cffi-0.16.0, the latest release. yt-dlp still refused to use it, and --list-impersonate-targets came back with every target marked (unavailable). The debug line was the giveaway:
[debug] Optional libraries: ... curl_cffi-0.16.0 (unsupported) ...
yt-dlp doesn't just check that curl_cffi is importable — it pins a narrow set of versions it actually trusts, because curl_cffi's impersonation API has changed shape across releases. Digging through the installed yt-dlp build (it ships as a zip-app; python -m zipfile opens it like any other zip) turned up the exact check:
curl_cffi_version = tuple(map(int, re.split(r'[^\d]+', curl_cffi.__version__)[:3]))
if curl_cffi_version != (0, 5, 10) and not (0, 10) <= curl_cffi_version < (0, 16):
curl_cffi._yt_dlp__version = f'{curl_cffi.__version__} (unsupported)'
raise ImportError('Only curl_cffi versions 0.5.10 and 0.10.x through 0.15.x are supported')
Latest-and-greatest was one minor version too new. The fix was to pin below the ceiling:
python3 -m pip install "curl_cffi>=0.10,<0.16"
Lesson: when a library depends on another native-extension package for an optional feature, don't assume "latest" is "supported" — check for a version gate specifically on the feature you're trying to turn on, not just on whether the import succeeds.
Collateral damage from --force-reinstall
The first install attempt used --force-reinstall to make sure the correct version actually took. It did — but it also silently upgraded packages that were already correctly pinned for something else entirely: certifi, cffi, markdown-it-py, and pygments all jumped to newer versions than another tool in the same shared Python environment required. pip said so plainly, right at the end of the install:
ERROR: pip's dependency resolver does not currently take into account all
the packages that are installed. This behaviour is the source of the
following dependency conflicts.
some-other-tool 2.15.0 requires certifi==2026.4.22, but you have
certifi 2026.7.22 which is incompatible.
some-other-tool 2.15.0 requires cffi==2.0.0, but you have cffi 2.1.1 ...
None of those four packages actually needed upgrading — curl_cffi's own requirement (cffi>=2.0.0, certifi>=2024.2.2) was already satisfied by the versions in place. --force-reinstall doesn't just reinstall the package you named; it re-resolves and reinstalls its entire dependency subtree, and when it does that it grabs the latest version satisfying each constraint rather than leaving an already-satisfied pin alone. In a shared base environment with several unrelated tools installed, that's a quiet way to break something you weren't even looking at.
The fix was to pin those four straight back:
python3 -m pip install "certifi==2026.4.22" "cffi==2.0.0" \
"markdown-it-py==4.0.0" "pygments==2.20.0"
Lesson: avoid --force-reinstall in a shared or base Python environment. If you need a specific version of one package, ask for exactly that (pip install "pkg==x.y.z") rather than forcing a full dependency re-resolve you don't control.
A new error, and a wrong theory
With the right curl_cffi version in place, the impersonation warning was gone — replaced by a new one, appearing on every single attempt:
SSLError: Failed to perform, curl: (35) TLS connect error:
error:00000000:invalid library (0):OPENSSL_internal:invalid library (0)
This looks exactly like a known class of curl_cffi bug: its bundled BoringSSL build can clash with another OpenSSL-family library already loaded in the same Python process — commonly cryptography, pulled in transitively by something unrelated like browser-cookie decryption support. The natural fix is isolation: a clean virtual environment with only yt-dlp and curl_cffi, nothing that could drag in a conflicting crypto library.
We built that environment. Same error, 100% reproducible, in complete isolation — no cryptography, no secretstorage, nothing else installed at all.
That result was more useful than a fix would have been. A clean-environment reproduction rules out an entire category of theories in one shot. It wasn't a Python packaging conflict. It had to be something outside Python's import graph.
Stripping back to the dumbest possible test
The next step was to remove every layer of abstraction and test the actual thing that was failing: a TLS handshake over the proxy. Not curl_cffi. Not even yt-dlp. Plain, unimpersonated requests:
import requests
r = requests.get("https://www.example.com",
proxies={"https": "http://127.0.0.1:8082"})
Same SSL error. Against a domain with nothing to do with the original site. That was the moment the whole investigation flipped: this was never a curl_cffi bug, or even a Python bug. Something about the proxy itself was breaking HTTPS.
Raw curl confirmed it in the plainest terms available:
* CONNECT tunnel established, response 200
> Client hello (1)
* OpenSSL SSL_connect: SSL_ERROR_SYSCALL in connection to www.example.com:443
The proxy's CONNECT tunnel came up cleanly — a 200 Connection Established — and then the TLS handshake died immediately afterward, for every client, on every site. Nothing about curl_cffi's TLS fingerprint mattered; the tunnel itself wasn't relaying bytes correctly once real TLS traffic started flowing through it.
The proxy client had several selectable exit gateways by region. Switching from one region to another and rerunning the exact same curl command fixed it instantly — clean 200, valid response, every time. The original gateway node was in a broken state for HTTPS relaying specifically; nothing in our code, yt-dlp, or curl_cffi was ever going to fix that from the client side.
What actually shipped
Three real problems, three real fixes, none of them where the first error message pointed:
The lasting fix was to isolate curl_cffi-enabled yt-dlp into its own virtual environment — so a future dependency bump anywhere else on the machine can never touch it again — and to symlink the PATH-resolved yt-dlp at that environment's build, so every caller picks it up without extra configuration.
The general shape of this kind of bug
None of the individual fixes here are exotic. What made this one take longer than it should have was trusting each error message's own framing for too long:
- "410 Gone" told us the video was gone. It wasn't — it was an anti-bot response wearing the shape of a different error.
- "unsupported" told us the install was broken. It wasn't broken, it was one version too new for a pin we didn't know existed.
- "invalid library" pointed straight at
curl_cffiand OpenSSL. It was neither — it was a proxy gateway with a bad TLS relay.
The pattern that actually broke each false lead was the same one every time: reproduce with the fewest possible moving parts. A clean virtual environment ruled out the packaging theory. Plain requests with no impersonation ruled out curl_cffi entirely. Raw curl against an unrelated domain ruled out the target site. Each step removed one whole category of suspects instead of guessing at the next fix — and the actual root cause, when it finally showed up, wasn't in any of the code we'd been staring at.





Comments (0)
Leave a Comment