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.
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.
