The Fix
pip install urllib3==1.26.10
Based on closed urllib3/urllib3 issue #2637 · PR/commit linked
Production note: Most teams hit this during upgrades or environment changes. Roll out with a canary and smoke critical endpoints (health, OpenAPI/docs) before 100%.
@@ -607,13 +607,15 @@ def _ssl_wrap_socket_and_match_hostname(
context.load_default_certs()
- # Our upstream implementation of ssl.match_hostname()
- # only applies this normalization to IP addresses so it doesn't
- # match DNS SANs so we do the same thing!
$ curl -vvv -o /dev/null "https://[2606:4700:4700::1111%32]"
...
* Server certificate:
* subject: C=US; ST=California; L=San Francisco; O=Cloudflare, Inc.; CN=cloudflare-dns.com
* start date: Oct 25 00:00:00 2021 GMT
* expire date: Oct 25 23:59:59 2022 GMT
* subjectAltName: host "2606:4700:4700::1111" matched cert's IP address! <--- here is the confirmation
* issuer: C=US; O=DigiCert Inc; CN=DigiCert TLS Hybrid ECC SHA384 2020 CA1
* SSL certificate verify ok.
Re-run the minimal reproduction on your broken version, then apply the fix and re-run.
Option A — Upgrade to fixed release\npip install urllib3==1.26.10\nWhen NOT to use: This fix should not be used if the application requires strict hostname verification including zone IDs.\n\nOption C — Workaround\nand simply strip the zone ID prior to passing it to OpenSSL -- I think it would be harmless and I'm in favor of this. It also turns out that urllib3's `match_hostname` implementation (used for `assert_hostname`) is wrong and compares `ipaddress.IPAddress` objects directly, not their `bytes` encoded form, which would also cause issues with zone IDs.\nWhen NOT to use: This fix should not be used if the application requires strict hostname verification including zone IDs.\n\n
Why This Fix Works in Production
- Trigger: urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='2606:4700:4700::1111%32', port=443):
- Mechanism: IPv6 zone ID was included in certificate hostname matching, causing mismatches
- Why the fix works: Works around issues with IPv6 scoped addresses and TLS server identity verification by stripping the zone ID from hostnames before matching certificates. (first fixed release: 1.26.10).
- If left unfixed, the same config can fail only in production (env differences), causing startup failures or partial feature outages.
Why This Breaks in Prod
- Shows up under Python 3.8.6 in real deployments (not just unit tests).
- IPv6 zone ID was included in certificate hostname matching, causing mismatches
- Surfaces as: urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='2606:4700:4700::1111%32', port=443):
Proof / Evidence
- GitHub issue: #2637
- Fix PR: https://github.com/urllib3/urllib3/pull/2638
- First fixed release: 1.26.10
- Reproduced locally: No (not executed)
- Last verified: 2026-02-09
- Confidence: 0.85
- Did this fix it?: Yes (upstream fix exists)
- Own content ratio: 0.60
Discussion
High-signal excerpts from the issue thread (symptoms, repros, edge-cases).
“@delroth Thanks for digging through all the standards for this, I wasn't able to find a reference myself that mentions the IP address comparison so…”
“Actually RFC 9110 (literally released 4 days ago!) seems to properly define this for HTTP over TLS too: > 4.3.4”
“I actually think the bug is in OpenSSL or CPython, though it could be worked around in urllib3”
Failure Signature (Search String)
- urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='2606:4700:4700::1111%32', port=443):
Error Message
Stack trace
Error Message
-------------
urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='2606:4700:4700::1111%32', port=443):
Max retries exceeded with url: / (Caused by SSLError(CertificateError("hostname
'2606:4700:4700::1111%32' doesn't match either of 'cloudflare-dns.com', '*.cloudflare-dns.com',
'one.one.one.one', '1.1.1.1', '1.0.0.1', '162.159.36.1', '162.159.46.1',
'2606:4700:4700:0:0:0:0:1111', '2606:4700:4700:0:0:0:0:1001', '2606:4700:4700:0:0:0:0:64',
'2606:4700:4700:0:0:0:0:6400'")))
Minimal Reproduction
$ curl -vvv -o /dev/null "https://[2606:4700:4700::1111%32]"
...
* Server certificate:
* subject: C=US; ST=California; L=San Francisco; O=Cloudflare, Inc.; CN=cloudflare-dns.com
* start date: Oct 25 00:00:00 2021 GMT
* expire date: Oct 25 23:59:59 2022 GMT
* subjectAltName: host "2606:4700:4700::1111" matched cert's IP address! <--- here is the confirmation
* issuer: C=US; O=DigiCert Inc; CN=DigiCert TLS Hybrid ECC SHA384 2020 CA1
* SSL certificate verify ok.
Environment
- Python: 3.8.6
- urllib3: 1.26.9
What Broke
Users experienced SSL certificate errors when connecting to IPv6 addresses with zone IDs.
Why It Broke
IPv6 zone ID was included in certificate hostname matching, causing mismatches
Fix Options (Details)
Option A — Upgrade to fixed release Safe default (recommended)
pip install urllib3==1.26.10
Use when you can deploy the upstream fix. It is usually lower-risk than long-lived workarounds.
Option C — Workaround Temporary workaround
and simply strip the zone ID prior to passing it to OpenSSL -- I think it would be harmless and I'm in favor of this. It also turns out that urllib3's `match_hostname` implementation (used for `assert_hostname`) is wrong and compares `ipaddress.IPAddress` objects directly, not their `bytes` encoded form, which would also cause issues with zone IDs.
Use only if you cannot change versions today. Treat this as a stopgap and remove once upgraded.
Fix reference: https://github.com/urllib3/urllib3/pull/2638
First fixed release: 1.26.10
Last verified: 2026-02-09. Validate in your environment.
When NOT to Use This Fix
- This fix should not be used if the application requires strict hostname verification including zone IDs.
Verify Fix
Re-run the minimal reproduction on your broken version, then apply the fix and re-run.
Did This Fix Work in Your Case?
Quick signal helps us prioritize which fixes to verify and improve.
Prevention
- Add a CI check that diffs key outputs after upgrades (OpenAPI schema snapshots, JSON payload shapes, CLI output).
- Upgrade behind a canary and run integration tests against the canary before 100% rollout.
- Add a TLS smoke test that performs a real handshake in CI (include CA bundle validation and hostname checks).
- Alert on handshake failures by error string and endpoint to catch cert/CA changes quickly.
Version Compatibility Table
| Version | Status |
|---|---|
| 1.26.10 | Fixed |
Related Issues
No related fixes found.
Sources
We don’t republish the full GitHub discussion text. Use the links above for context.