Views: 19

ssl.SSLCertVerificationError: certificate verify failed

ssl.SSLCertVerificationError: certificate verify failed: Python can't trust the chain. Check certifi and the server intermediate. Free instant check.

Check your domain for this issue now

Free, no sign-up. Runs the exact check this guide describes and shows what to fix.

Problem

Python stops with ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1006) and refuses to complete the TLS connection. This is OpenSSL’s verify result 20 (X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY) surfaced through Python’s ssl module. Python received a certificate but could not build a chain from it back to a certificate authority it trusts. The certificate is usually fine. What’s missing is a link — and there are only two places that link can be missing: on the server (it didn’t send the intermediate that connects its leaf to a public root) or on your machine (Python’s trust store doesn’t contain the root, or something re-signed the traffic with a CA Python has never heard of). The whole job is telling those two apart.

Symptoms

  • The full message ends in a reason. unable to get local issuer certificate (verify error 20) is the common one — a chain that doesn’t reach a trusted root. certificate has expired (error 10), self-signed certificate in certificate chain (error 19, a private or interception CA in the path), and self-signed certificate (error 18, the endpoint presented its own root) point at different problems, so read the tail of the message before doing anything.
  • It shows up through whatever library sits on ssl: requests reports SSLError(... CERTIFICATE_VERIFY_FAILED ...), urllib/pip/httpx wrap the same underlying error.
  • If it fails for one host but others work: the problem is that host’s chain — almost always a missing intermediate on the server.
  • If it fails for every HTTPS host, or right after installing Python on macOS: the problem is Python’s trust store on your machine, not any server.

Top 3 Causes

  1. Python isn’t reading a valid CA bundle (the macOS/Windows classic) - A python.org build carries its own OpenSSL and trusts roots from the certifi bundle, not the OS store. On a fresh macOS install the bundle isn’t wired up until you run the installer’s Install Certificates.command, so every HTTPS request fails until you do. The same thing bites minimal Docker images and freshly provisioned Linux boxes with no ca-certificates package, and any environment where certifi is old enough to predate a root rotation.
  2. The server isn’t sending its intermediate certificate - The most common cause when one specific host fails while others work. The server presents only its leaf and omits the intermediate that chains it to a trusted root. Browsers often paper over this by fetching the missing intermediate on their own (AIA fetching); Python does not, so it can’t complete the chain and stops with error 20. The fix is on the server — deploy the full chain, not the leaf alone.
  3. A proxy or antivirus is intercepting TLS - Corporate middleboxes and “HTTPS scanning” antivirus decrypt your traffic and re-sign it with their own private CA. That CA is installed in the OS store on a managed machine, but Python reads certifi, not the OS store, so it sees a certificate signed by an issuer it can’t find — typically self-signed certificate in certificate chain (error 19). On an unmanaged machine the same signature is what a real attacker would produce, which is why Python refuses it.

Diagnose with DechoNet

  • SSL Check shows the exact chain the server sends over the wire — whether the intermediate is present or the server is shipping the leaf alone. If the chain is incomplete here, you’ve confirmed cause #2 and the fix is on the server. If the chain looks complete from the public internet but Python still fails on your machine, the problem is local: your certifi bundle or a proxy re-signing your traffic (cause #1 or #3).
  • HTTP Check confirms the endpoint actually responds once the certificate question is set aside, so you don’t chase a trust error on a host that’s also down.

Resolution Checklist

  • Read the scope first. One host failing while others work → server chain problem. Every host failing → Python’s trust store. This one question routes the entire fix.
  • On a fresh python.org install on macOS, run the bundled command once: /Applications/Python\ 3.x/Install\ Certificates.command (substitute your version). It pip-installs certifi and points Python’s default verification at it. This alone fixes the “every host fails right after installing Python” case.
  • Elsewhere, refresh the bundle: pip install --upgrade certifi, then confirm what Python is actually reading with python -c "import ssl; print(ssl.get_default_verify_paths())" and python -c "import certifi; print(certifi.where())".
  • To point a specific library at a known-good bundle, pass it explicitly: requests.get(url, verify=certifi.where()), or set SSL_CERT_FILE / REQUESTS_CA_BUNDLE to the bundle path for tools that read the environment. This keeps verification on, unlike verify=False.
  • For one failing host, look at the served chain — openssl s_client -connect YOUR_DOMAIN:443 -servername YOUR_DOMAIN -showcerts. If you see only one CERTIFICATE block, the intermediate is missing; reinstall the certificate as a fullchain bundle on the server and re-run SSL Check to confirm the intermediate now appears.
  • For a corporate proxy you’re supposed to trust, add its root to the bundle Python reads (append the proxy CA’s PEM, or set REQUESTS_CA_BUNDLE to a file that includes it). If you don’t recognize the intercepting CA, stop and treat it as suspicious rather than trusting it blindly.

When to Escalate

  • If SSL Check shows a complete, correct chain from the public internet but Python still fails only on one machine, the CA doing the re-signing is on that machine’s network — a proxy or antivirus. That’s an endpoint/IT question (which root to trust and how to distribute it), not a per-script flag.
  • If the endpoint genuinely uses a private or self-signed CA (an internal service, an appliance, a dev environment), trust that specific CA via verify=/path/to/ca.pem or by adding it to certifi — don’t turn verification off globally. Reaching for verify=False or NODE_TLS_REJECT_UNAUTHORIZED-style escape hatches trains you to ignore the one signal that separates your real server from an impostor.

Related Tools

Related Guides

Share this guide

[Ad] Guide Detail Inline
← Back to All Guides