Skip to content
Nilay Kabariya

Entry No.094·Fixes··3 min read

Python CERTIFICATE_VERIFY_FAILED, tested on 3.12 to 3.15

certificate verify failed: unable to get local issuer certificate, or Basic Constraints of CA cert not marked critical on Python 3.13+. Each cause tested.

The error, verbatim

Jump to the fix ↓
requests.exceptions.SSLError: HTTPSConnectionPool(host='pypi.org', port=443): Max retries exceeded with url: /simple/
(Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed:
unable to get local issuer certificate (_ssl.c:1144)')))
✓ Reproduced on Windows 11 Pro with Avast HTTPS scanning; Ubuntu on GitHub Actions

Tested on

Python
3.12.15, 3.13.17, 3.14.5, 3.14.9, 3.15.1, 3.15.2
Libraries
requests 2.34.2, certifi 2026.10.23, truststore, pip 26.1.1 and 26.2.2
OpenSSL
3.0.20 and 3.5.9 (Windows), 3.5.5 (Linux)
OS
Windows 11 Pro with Avast HTTPS scanning; Ubuntu on GitHub Actions
Contents
  1. 1. “unable to get local issuer certificate”: antivirus or a proxy01
  2. 2. “Basic Constraints of CA cert not marked critical”: Python 3.13 and newer02
  3. How this was tested03

CERTIFICATE_VERIFY_FAILED means Python couldn’t trace the server’s certificate back to a root it trusts. The tail of the message tells you why, and the two I reproduced need different fixes:

Message ends with Cause Go to
unable to get local issuer certificate Something between you and the server re-signs HTTPS (antivirus, a company proxy), and Python doesn’t trust its root 1
Basic Constraints of CA cert not marked critical You do trust that root, but it’s built in a way Python 3.13+ refuses 2

1. “unable to get local issuer certificate”: antivirus or a proxy

My Windows laptop runs Avast, which scans HTTPS by re-signing every site’s certificate with its own root, “Avast Web/Mail Shield Root”. Python saw that as pypi.org’s issuer:

{'organizationalUnitName': 'generated by Avast Antivirus for SSL/TLS scanning', 'organizationName': 'Avast Web/Mail Shield', 'commonName': 'Avast Web/Mail Shield Root'}

Avast adds its root to the Windows certificate store, so browsers are fine. What Python does depends on the library. On Python 3.14.5 and 3.15.2, in fresh virtual environments:

Library Result
urllib.request (built in) ✅ worked
pip install ✅ worked
requests ❌ certificate verify failed: unable to get local issuer certificate

The built-in ssl module on Windows also loads the Windows certificate store, and pip uses that store through truststore. requests trusts only the list in the certifi package, which has never heard of Avast. That’s why pip can install requests, and requests then fails on the same site.

Have requests (and everything else) use the operating system’s certificate store:

pip install truststore
import truststore
truststore.inject_into_ssl()

import requests
requests.get("https://pypi.org/simple/")

If you can’t change the code, point requests at a bundle file that contains the extra root:

set REQUESTS_CA_BUNDLE=C:\path\to\bundle.pem

Don’t turn verification off (verify=False) to get past this. It hides the error and also hides a real interception, which is exactly what this check exists to catch.

2. “Basic Constraints of CA cert not marked critical”: Python 3.13 and newer

This one arrives with an upgrade. Your code trusts a company or proxy root, it worked on Python 3.12, and on 3.13 it fails:

urllib.error.URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: Basic Constraints of CA cert not marked critical (_ssl.c:1032)>

Python 3.13 turned on a stricter certificate check by default, the VERIFY_X509_STRICT flag. A CA certificate is supposed to mark its “Basic Constraints” field as critical, and a lot of in-house roots don’t. To test it, I made a root without that marking, signed a local HTTPS server with it, and told Python to trust the root:

Python Trust the root with cafile= / verify= / SSL_CERT_FILE Root in the system store
3.12.15 ✅ ✅
3.13.17 ❌ not marked critical ❌ not marked critical
3.14.9 ❌ not marked critical ❌ not marked critical
3.15.1 ❌ not marked critical ❌ not marked critical

The root itself is fine for 3.12; only the stricter check rejects it. truststore didn’t help here on Linux: with the root in the system store, it failed on 3.13 and newer with the same message.

The real fix is a root that’s built correctly. If it’s your company’s, ask whoever runs the proxy to reissue it with basicConstraints marked critical. Until then, clear the strict flag for the connections that need that root, and only those:

import ssl
ctx = ssl.create_default_context(cafile="company-root.pem")
ctx.verify_flags &= ~ssl.VERIFY_X509_STRICT

For requests, pass that context through an adapter:

import ssl, requests
from requests.adapters import HTTPAdapter

class CompanyRoot(HTTPAdapter):
    def init_poolmanager(self, *args, **kwargs):
        ctx = ssl.create_default_context(cafile="company-root.pem")
        ctx.verify_flags &= ~ssl.VERIFY_X509_STRICT
        kwargs["ssl_context"] = ctx
        return super().init_poolmanager(*args, **kwargs)

session = requests.Session()
session.mount("https://", CompanyRoot())

The behind-Avast case in section 1 is not this one. Avast’s root passed the strict check: with the bundle, urllib worked on 3.14 and 3.15 with the flag still on.

How this was tested

October 31, 2026. Section 1: Windows 11 Pro with Avast’s Web Shield scanning HTTPS, Python 3.14.5 (OpenSSL 3.0.20) and 3.15.2 (OpenSSL 3.5.9), each in a new virtual environment with no certificate environment variables set, requesting https://pypi.org/simple/. Section 2: GitHub Actions ubuntu-latest with Python 3.12.15, 3.13.17, 3.14.9 and 3.15.1: an OpenSSL-generated root with basicConstraints=CA:TRUE not marked critical, a server certificate signed by it for localhost, and Python’s own HTTPS server, then the same checks, and again after adding the root with update-ca-certificates. The scripts and the output are public.

— N.K., end of entry No.094

Useful? Pass it on:Post on XFollow @EmotionalMatter

Related entries

  1. No.064

    Fix: Microsoft Visual C++ 14.0 or greater is required

    pip install fails on Windows asking for Microsoft C++ Build Tools. Four causes reproduced, Failed building wheel for pyyaml fixed, and when Build Tools help.

    > error: Microsoft Visual C++ 14.0 or greater is required. Get it with "Microsoft C++ Build Tools": https://visualstudio.microsoft.com/visual-cpp-build-tools/

    FIXED7 minWindows
  2. No.093

    Fix: bigint Failed to load bindings, pure JS will be used

    bigint: Failed to load bindings, pure JS will be used. Why npm 12 shows it on every install, whether it matters, and how to get the native build back.

    > bigint: Failed to load bindings, pure JS will be used (try npm run rebuild?)

    FIXED3 minWindows
  3. No.075

    Python was not found, or pip not recognized (Windows fix)

    "Python was not found" comes from a Microsoft Store placeholder python.exe, not Python, and exits with 9009. How to see which python.exe wins, and the pip fix.

    > Python was not found; run without arguments to install from the Microsoft Store, or disable this shortcut from Settings > Apps > Advanced app settings > App execution aliases.

    FIXED3 minWindows

Post card · Every Sunday

What broke this week.

One email on Sundays: the Node, TypeScript, Python and AI SDK releases that broke builds that week, each one reproduced, with a link to the fix that worked. Never shared, and you can leave any time.

— Nilay

or follow by RSSor on X

By subscribing you agree to the privacy note. One click to leave.

tip: paste the exact error text