• 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

  • ssl and urllib use ssl.create_default_context(). On Windows it loads the Windows certificate store; on Linux it uses OpenSSL’s default bundle, which you can override with the SSL_CERT_FILE environment variable.
  • requests uses the certifi bundle, not the operating system’s store, and ignores SSL_CERT_FILE. Set REQUESTS_CA_BUNDLE, or pass verify='/path/to/ca.pem' (requests documentation).
  • pip 24.2 and later uses the system certificates in addition to certifi; --cert or PIP_CERT point 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

Try it: Chain checkerOpen the full tool

Runs in your browser. Nothing you paste is sent anywhere.

Every certificate the server sends, leaf first. The output of openssl s_client -showcerts -connect example.com:443 works as it is: we ignore the text around the certificates.

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

In the gameLevel 12: Who do we trust?Registry of Roots, Keystone. In development

Early edition. Checked against the standards; an independent expert review is in progress. Found a mistake? Tell us.

Last reviewed Edition 0.9