Skip to content
ecdsa.com
Python ssl module (OpenSSL)Python cryptography

Error message

[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate

Also printed as

  • ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate

Python appends the C source location, e.g. "(_ssl.c:1082)"; the number changes with the Python version and means nothing for diagnosis. urllib wraps the error as "<urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] …>", requests and urllib3 as "Max retries exceeded … (Caused by SSLError(…))".

Python ssl module (OpenSSL) — ssl.SSLCertVerificationError, X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY (20)

What it means

Python's ssl module asked OpenSSL to verify the server's certificate, and OpenSSL could not find the issuer of the last certificate in the chain among the CA certificates this SSLContext loaded. The server is not necessarily misconfigured: often this Python simply looks at a different (or empty) CA bundle than your browser or operating system does.

Why it happens

How to fix it

  1. 1.

    macOS python.org builds: run Install Certificates.command

    The script installs certifi and links it as the bundle of that Python's OpenSSL. It must be run again after installing another Python version.

    bash
    # macOS, python.org installer: run once per installed Python version
    "/Applications/Python 3.14/Install Certificates.command"
  2. 2.

    Use the operating system's trust store with truststore

    truststore makes ssl.SSLContext verify through the OS (Keychain on macOS, the Windows certificate store, the system bundle on Linux), so roots deployed by your IT department work in Python too. It also covers urllib3, requests and pip, which uses it by default since pip 24.2.

    python
    # pip install truststore==0.10.5  — verify with the OS trust store (Keychain, Windows store, Linux bundle)
    import truststore
    truststore.inject_into_ssl()   # call once at start-up, before creating connections
    
    import urllib.request
    urllib.request.urlopen("https://api.example.com/")
  3. 3.

    Load the CA explicitly for one client

    Pass cafile (or cadata) to ssl.create_default_context() for code that talks to a private service. The context still verifies hostnames and dates.

    python
    import ssl, urllib.request
    
    ctx = ssl.create_default_context(cafile="/etc/ssl/private-ca/corp-root-ca.pem")
    urllib.request.urlopen("https://api.example.com/", context=ctx)
  4. 4.

    Check and set the default bundle path

    ssl.get_default_verify_paths() shows which file and directory OpenSSL uses. SSL_CERT_FILE and SSL_CERT_DIR override them for the process; point them at a bundle that contains both public roots and your CA.

    bash
    # Where does this Python look by default?
    python3 -c "import ssl; print(ssl.get_default_verify_paths())"
    # Point OpenSSL-based clients at a bundle that includes your CA
    export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
  5. 5.

    Server owners: send the full chain

    If the handshake contains only one certificate and its issuer is a public intermediate, configure the server with the full-chain file.

    bash
    # Server side: 1 certificate in the handshake = intermediate missing
    openssl s_client -connect api.example.com:443 -servername api.example.com -showcerts </dev/null 2>/dev/null \
      | grep -c 'BEGIN CERTIFICATE'

Related errors

Browse the full signature error database