Error message
[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate in certificate chain
Also printed as
- [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self signed certificate in certificate chain
OpenSSL 3 prints "self-signed", OpenSSL 1.1.1 "self signed"; Python appends a source location such as "(_ssl.c:1082)". Through urllib it appears inside "<urlopen error …>", through requests inside "Max retries exceeded … (Caused by SSLError(…))".
Python ssl module (OpenSSL) — ssl.SSLCertVerificationError, X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN (19)
What it means
The chain the server presented ends in a self-signed root certificate that is not among the CAs this Python process trusts. The chain itself is complete; the root is simply unknown. When it happens for public sites like pypi.org or api.github.com, the root belongs to a TLS-inspecting proxy or antivirus on your network or machine.
Why it happens
A corporate proxy or antivirus intercepts HTTPS
commonThe interception root is installed in the Windows or macOS store by IT, so browsers work. Python's OpenSSL, and libraries that use the certifi bundle, do not read those stores.
requests, pip or httpx verify with certifi
commoncertifi contains only Mozilla's public roots. Adding the company root to the system store does not change certifi's file, and a pip upgrade of certifi overwrites manual edits.
An internal service sends its private root
occasionalInternal APIs signed by a company CA that serve the root in the chain still need that root configured on the client.
conda, pyenv or a virtual environment with its own bundle
occasionalconda ships its own OpenSSL and CA bundle; a fix applied to the system Python does not reach it.
How to fix it
- 1.
Find out whose root it is
Print the chain for a public host. A root named after your company or a security product is the one to trust; an unknown name on an untrusted network is a reason to stop, not to trust it.
bash openssl s_client -connect pypi.org:443 -servername pypi.org -showcerts </dev/null 2>/dev/null \ | grep -E '^ *[0-9]+ s:|^ *i:' # A top certificate named after your company or a security vendor = interception proxy - 2.
Verify with the operating system's trust store
truststore.inject_into_ssl() makes Python's TLS use the OS trust store, which already contains roots deployed by your organisation. It is the cleanest fix on managed Windows and macOS machines.
python # pip install truststore==0.10.5 import truststore truststore.inject_into_ssl() # the proxy root deployed in the OS store is now trusted import requests requests.get("https://api.example.com/") - 3.
Build a bundle with public roots plus your root
REQUESTS_CA_BUNDLE (requests) and SSL_CERT_FILE (ssl, urllib, httpx) replace the default bundle, so concatenate certifi's roots with the company root rather than pointing at the company root alone.
bash # Keep the public roots and add the company/proxy root cat "$(python3 -m certifi)" corp-root-ca.pem > ~/.config/corp-ca-bundle.pem export REQUESTS_CA_BUNDLE=~/.config/corp-ca-bundle.pem # requests export SSL_CERT_FILE=~/.config/corp-ca-bundle.pem # ssl, urllib, httpx - 4.
conda: configure its own setting
conda reads ssl_verify from .condarc; set it to the combined bundle so conda install and conda update work behind the proxy.
bash # conda keeps its own OpenSSL and CA bundle conda config --set ssl_verify /path/to/corp-ca-bundle.pem
Related errors
[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local…Python ssl module (OpenSSL)[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certifi…Python ssl module (OpenSSL)There was a problem confirming the ssl certificate: [SSL: CERTIFICATE_VERIFY_FA…pipcurl: (60) SSL certificate problem: self-signed certificate in certificate chaincurl (OpenSSL backend)