Python

How to Resolve "Python SSL: CERTIFICATE_VERIFY_FAILED" With urllib or requests

4 min read by DebuggedIt

Quick answer

An HTTPS request from Python fails with a certificate verification error, even though the same URL loads fine in a browser. This is Python's SSL layer refusing...

An HTTPS request from Python fails with a certificate verification error, even though the same URL loads fine in a browser. This is Python's SSL layer refusing to trust the server's certificate β€” the fix depends on whether Python's own trusted certificate store is out of date, missing entirely, or genuinely being intercepted by something like a corporate proxy.

The Problem

A normal HTTPS request fails with a certificate chain error:

>>> import requests
>>> requests.get("https://example.com")
requests.exceptions.SSLError: HTTPSConnectionPool(host='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:1006)')))

With urllib directly, the error is more terse but points at the same root cause:

urllib.error.URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:997)>

Why It Happens

Python verifies HTTPS certificates against a bundle of trusted root certificate authorities, and this error means it couldn't build a valid chain of trust from the server's certificate back to something in that bundle. The specific cause varies:

  • macOS-specific missing certificates β€” the official python.org installer on macOS doesn't automatically link into the system's certificate store, leaving Python with no usable CA bundle at all until a setup script is run.
  • An outdated certifi package (the CA bundle library requests depends on) that doesn't yet include a newer certificate authority the target server uses.
  • A corporate network intercepting HTTPS traffic via a TLS-inspecting proxy, presenting its own internal certificate that isn't in any public trust store.
  • The server's own certificate chain is genuinely incomplete or misconfigured, missing intermediate certificates β€” the same underlying issue browsers sometimes tolerate more gracefully than strict clients do.
  • The system clock is significantly wrong, causing otherwise-valid certificates to appear expired or not-yet-valid during the verification check.

The Fix

If you're on macOS with the official python.org installer, run the certificate installation script that ships alongside it β€” this is far and away the most common cause on macOS specifically:

/Applications/Python\ 3.12/Install\ Certificates.command

For other platforms, or if that doesn't resolve it, make sure certifi is current, since requests relies on it for its default CA bundle:

pip install --upgrade certifi

Verify which CA bundle Python is actually using:

python -c "import certifi; print(certifi.where())"

Test the connection directly against that bundle to confirm whether it's a Python-side or server-side problem:

curl --cacert $(python -c "import certifi; print(certifi.where())") https://example.com

If you're behind a corporate proxy that intercepts HTTPS traffic with its own internal certificate, you'll need to explicitly trust that certificate rather than disabling verification entirely. Get the proxy's certificate from your IT department and point Python at it:

import requests
requests.get("https://example.com", verify="/path/to/corporate-ca-cert.pem")

Or configure it as an environment variable so it applies to every request without changing application code:

export REQUESTS_CA_BUNDLE=/path/to/corporate-ca-cert.pem
export SSL_CERT_FILE=/path/to/corporate-ca-cert.pem

Avoid disabling verification entirely as anything but a very temporary, local debugging step β€” setting verify=False defeats the entire purpose of HTTPS and exposes you to man-in-the-middle attacks, so it should never be used in production code:

# Only for isolated local debugging, never commit this
requests.get("https://example.com", verify=False)

Still Not Working?

If none of the above resolves it, check whether your system clock is significantly off, since certificate validity checks depend on accurate time and a sufficiently wrong clock can cause a currently-valid certificate to fail verification as if it were expired or not yet valid:

date

Compare this against the actual current time from a reliable source. If it's noticeably wrong, sync it via NTP:

sudo timedatectl set-ntp true

If the server's certificate chain itself is genuinely broken (missing intermediates on the server side, similar to the same issue covered for Nginx configurations), verify independently of Python using OpenSSL, which gives a much more detailed diagnostic of exactly where the chain breaks down:

openssl s_client -connect example.com:443 -showcerts </dev/null 2>/dev/null | grep "Verify return code"

If this reports anything other than 0 (ok), the problem is on the server's end, not in your Python environment, and the fix belongs in that server's certificate configuration rather than anything on your local machine.