- Python
- Exact text checked with the real tool
[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate
Also seen in: requests, urllib, pip, aiohttp, boto3
In one sentence
Python could not link the server's certificate to a trusted root: the server omits its intermediate, or the company or proxy root is missing from the store Python uses.
Where you see it
In any Python code that opens an HTTPS connection: urllib, requests, pip, SDKs built on them. The exception is ssl.SSLCertVerificationError (see the ssl module documentation); the number after _ssl.c: changes between Python versions.
urllib.error.URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1028)>
With requests:
requests.exceptions.SSLError: HTTPSConnectionPool(host='api.example.com', port=443): Max retries exceeded with url: / (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1028)')))
The same family includes certificate verify failed: self-signed certificate in certificate chain (a complete chain ending in an untrusted root). Recent pip on Windows may show the operating system’s wording instead: A certificate chain processed, but terminated in a root certificate which is not trusted by the trust provider.
What it means
Python checks the server’s certificate chain with OpenSSL. It needs the issuer of every certificate, from what the server sent or from its trust store. It reached a certificate whose issuer it could not find, so it could not connect the server to a trusted root. Which store Python uses depends on the library, and that is why one script works and the next fails.
Likely causes
The server does not send its intermediate certificate
Browsers can fetch a missing intermediate certificate or may already have it; Python does not. Count what the server sends:
openssl s_client -connect api.example.com:443 -servername api.example.com -showcerts </dev/null 2>/dev/null | grep -c 'BEGIN CERTIFICATE'
1 means only the server’s own certificate. Fix it on the server: serve the certificate followed by the intermediate (fullchain.pem for nginx and certbot, SSLCertificateFile with the chain for Apache, the intermediate store on IIS). See verify error:num=21.
Your library uses a different store than you think
sslandurllibusessl.create_default_context(). On Windows it loads the Windows certificate store; on Linux it uses OpenSSL’s default bundle, which you can override with theSSL_CERT_FILEenvironment variable.requestsuses thecertifibundle, not the operating system’s store, and ignoresSSL_CERT_FILE. SetREQUESTS_CA_BUNDLE, or passverify='/path/to/ca.pem'(requests documentation).- pip 24.2 and later uses the system certificates in addition to certifi;
--certorPIP_CERTpoint it at a bundle (pip documentation). - Python from the python.org installer on macOS needs one extra step: double-click “Install Certificates.command” in its folder in Applications, which installs root certificates for that Python (Using Python on macOS).
Find out where ssl looks:
python -c "import ssl; print(ssl.get_default_verify_paths())"
The root is a private CA or a TLS-inspection proxy
On a company network, internal servers use a company CA, and a TLS-inspection proxy may re-sign public sites. Check the issuer of a public site:
openssl s_client -connect pypi.org:443 -servername pypi.org </dev/null 2>/dev/null | openssl x509 -noout -issuer
If it names your company or a security product, get that root from IT (PEM format) and give it to Python. Because the custom bundle replaces the default for requests, include the public roots too:
cat "$(python -m certifi)" corp-root.pem > ca-bundle.pem
export REQUESTS_CA_BUNDLE="$PWD/ca-bundle.pem" # requests
export SSL_CERT_FILE="$PWD/ca-bundle.pem" # ssl and urllib
pip config set global.cert "$PWD/ca-bundle.pem" # pip
Better still, add the root to the operating system’s store (update-ca-certificates on Debian and Ubuntu, update-ca-trust extract on RHEL) and point the variables at the system bundle, for example /etc/ssl/certs/ca-certificates.crt on Debian and Ubuntu. Do not edit the cacert.pem inside the certifi package: the next upgrade overwrites it.
Python 3.13 is stricter
Python 3.13 turned on stricter X.509 checks in create_default_context(). Certificates from older private CAs can fail with messages such as certificate verify failed: Basic Constraints of CA cert not marked critical. The right fix is to reissue the CA or server certificates so they follow RFC 5280; see the What’s New in Python 3.13 notes.
Check yours
Paste the certificates from openssl s_client -showcerts. The checker shows whether the intermediate is missing or the chain ends at a root you need to trust.
Learn why
- Certificate chains and path validation
- Who do we trust? CAs, trust stores and self-signed certificates: why each language has its own store.
- Related: Hostname mismatch in Python and verify error:num=20.
Early edition. Checked against the standards; an independent expert review is in progress. Found a mistake? Tell us.