How to Fix Python "certificate verify failed" (SSLCertVerificationError)

VigilDog Team · October 18, 2026 · 6 min read

A script that worked yesterday suddenly throws ssl.SSLCertVerificationError: certificate verify failed, and every request grinds to a halt. It is one of the most common, and most misread, errors in Python networking. The reassuring part: the certificate verify failed python error almost always traces to a short list of concrete causes, and each one has a clean, honest fix that keeps verification turned on.

What the error actually means

When Python opens a TLS connection, it does not just check that the server has a certificate, it checks that the certificate chains up to a trusted root certificate authority (CA) it already knows about, that the chain is complete, that nothing has expired, and that the hostname matches. If any link in that walk fails, OpenSSL refuses the handshake and Python surfaces it as SSLCertVerificationError.

The key mental model is the chain. A server sends its leaf certificate plus any intermediate certificates. Your machine already trusts a set of root CAs. Verification succeeds only when Python can build an unbroken path from the leaf, through the intermediates, up to a root it trusts. Most 'certificate verify failed' errors are a break somewhere in that path, not a hacked server.

Root CAin the trust storeIntermediatemust be installedYour certificateleaf, for your domain
The TLS certificate chain of trust

Step 1: read the full error, not just the first line

The reason string at the end of the traceback is the single most useful piece of information, and people skip it. It tells you exactly which check failed:

Match the phrase to the cause before you touch anything. 'unable to get local issuer certificate' means the chain is incomplete or your CA store is missing an issuer, a trust-store problem. 'certificate has expired' means the leaf or an intermediate is past its validity date, a server problem. 'self signed certificate in certificate chain' usually means a corporate proxy is intercepting TLS. 'Hostname mismatch' means the certificate does not cover the name you connected to. Fixing the wrong one wastes an afternoon.

  • unable to get local issuer certificate, missing or outdated CA bundle / incomplete server chain
  • certificate has expired, the cert is genuinely past its notAfter date
  • self signed certificate in certificate chain, a proxy or firewall is re-signing traffic
  • Hostname mismatch, certificate is not valid for '…'wrong SAN / wrong host

The usual suspects and their fixes

Outdated certifi. The requests library and many others verify against the certifi CA bundle, not your operating system's store. If certifi is old, newer roots are missing. Upgrade it first: pip install --upgrade certifi. This single command resolves a surprising share of 'unable to get local issuer certificate' cases.

An incomplete server chain. If the fix above does not help and only one client complains, the server may be omitting intermediate certificates. Confirm it from the command line: openssl s_client -connect example.com:443 -servername example.com. Look for 'Verify return code'. If OpenSSL reports 'unable to get local issuer certificate' too, the fix belongs on the server (install the full chain), not in your code. You can confirm what a public host is serving with our SSL checker before you go editing Python.

An expired certificate. If the reason is 'certificate has expired', no client-side change is appropriate, the certificate genuinely needs renewing. Our guide on what to do when an SSL certificate has expired walks through renewal and the mistakes that cause repeat expirations.

macOS, corporate proxies, and custom CAs

Fresh macOS + python.org installs. The official python.org macOS build does not use the system keychain. It ships an 'Install Certificates.command' inside /Applications/Python 3.x/, run it once and the bundled certifi store is wired up. This is the classic 'brand new Mac, every HTTPS call fails' fix.

Corporate MITM proxies. Zscaler, Netskope, and many enterprise firewalls decrypt and re-sign TLS with an internal root CA. Python does not trust that root, so you see 'self signed certificate in certificate chain'. The correct fix is to trust your company's root explicitly, not to disable verification. Export the corporate root as a PEM file and point Python at it: set the environment variable REQUESTS_CA_BUNDLE=/path/to/corp-root.pem (for requests) or SSL_CERT_FILE=/path/to/corp-root.pem (for the stdlib ssl module and urllib). For a one-off in code, pass verify='/path/to/corp-root.pem' to requests, or build an ssl.SSLContext with ctx.load_verify_locations('/path/to/corp-root.pem').

Private / internal services. Same approach: hand Python the internal CA via SSL_CERT_FILE or load_verify_locations. You keep full verification, you just teach Python which authority to trust.

What not to do

The top search result for this error is almost always verify=False (or ssl._create_unverified_context). It makes the traceback disappear, and it also disables the exact protection TLS exists to provide: you can no longer tell the real server from an attacker on the network. It is fine for a throwaway experiment against a host you control; it is not fine in anything that ships, touches credentials, or runs in production.

If you are tempted to reach for it, stop and match the reason string from Step 1 to a real fix above. Ninety percent of the time it is 'upgrade certifi' or 'point at the right CA bundle', and both keep you verified.

Catch expiry before your users do

The one cause you cannot fix from the client'certificate has expired'is also the most preventable. Expired certificates do not fail quietly; they break every client at once, Python and browser alike, usually at the worst possible time. The fix is not code, it is a calendar you can trust.

VigilDog monitoring watches your certificates, their full chains, and expiry dates across every domain you run, and warns you well before anything lapses, so the next 'certificate verify failed' you debug is a config detail on your side, never a public outage on someone else's.

Questions

Frequently asked

Why does my Python script fail but my browser loads the same site fine?

Browsers ship their own trust store and often download missing intermediate certificates automatically; Python verifies against certifi's bundle and does not fetch missing intermediates. So an incomplete server chain or an outdated certifi will break Python while the browser papers over it.

Is pip install --upgrade certifi safe to run everywhere?

Yes. It only updates the bundled list of trusted root CAs that libraries like requests use for verification. It does not weaken security, it brings your trust store up to date, which is usually what fixes 'unable to get local issuer certificate'.

How do I trust a corporate proxy's certificate without disabling verification?

Export the proxy's root CA as a PEM file and point Python at it via the REQUESTS_CA_BUNDLE or SSL_CERT_FILE environment variable, or pass verify='/path/to/root.pem'. Verification stays on; you have simply added your company's authority to the trusted set.

Never debug an expired certificate in production again

VigilDog watches your certs, chains, and expiry dates across every domain and warns you before they lapse.

Your first domain is free forever