Skip to content
ecdsa.com
requests / urllib3 (Python)Python cryptography

Error message

Max retries exceeded with url: / (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate

Also printed as

  • requests.exceptions.SSLError: HTTPSConnectionPool(host='api.example.com', port=8443): Max retries exceeded with url: / (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate

Host, port and URL path are taken from your request. The innermost reason varies ("self-signed certificate in certificate chain", "Hostname mismatch…", "certificate has expired"), and the line ends with a source location such as "(_ssl.c:1082)')))".

requests / urllib3 (Python) — requests.exceptions.SSLError wrapping urllib3 MaxRetryError

What it means

requests asked urllib3 for a connection, the TLS handshake failed certificate verification, and urllib3 gave up (with requests' default of no retries, after the first attempt). The useful part is inside the parentheses: here OpenSSL could not find the issuer in the CA bundle requests uses, which by default is certifi's copy of the Mozilla roots, not the operating system's store.

Why it happens

How to fix it

  1. 1.

    Pass the right bundle with verify=

    verify accepts a path to a CA bundle file or a hashed CA directory; it keeps hostname and date checks on.

    python
    import requests
    
    # A CA file (or a directory prepared with c_rehash) for this call
    requests.get("https://api.example.com/", verify="/etc/ssl/certs/ca-certificates.crt")
  2. 2.

    Set REQUESTS_CA_BUNDLE for the process

    Pointing it at the system bundle (after installing your CA there) makes every requests call in that process trust the same roots as curl and the OS tools.

    bash
    # For every requests call in the process (also honoured: CURL_CA_BUNDLE)
    export REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt   # Debian/Ubuntu system bundle
    python3 app.py
  3. 3.

    Use the operating system's trust store

    truststore.inject_into_ssl() at start-up lets requests verify through the OS, so roots deployed by IT work without maintaining bundles.

    python
    # pip install truststore==0.10.5 — requests then verifies with the OS trust store
    import truststore
    truststore.inject_into_ssl()
    
    import requests
    requests.get("https://api.example.com/")
  4. 4.

    Configure a Session once

    For applications, set Session.verify to a bundle that contains public roots plus the company root, and reuse the session.

    python
    import requests
    
    session = requests.Session()
    session.verify = "/etc/ssl/private-ca/corp-ca-bundle.pem"   # public roots + company root
    session.get("https://api.example.com/")

Related errors

Browse the full signature error database