Fehlermeldung
Max retries exceeded with url: / (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate
Auch ausgegeben als
- 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 und URL-Pfad stammen aus deiner Anfrage. Der innerste Grund variiert („self-signed certificate in certificate chain“, „Hostname mismatch…“, „certificate has expired“), und die Zeile endet mit einer Quellcode-Position wie „(_ssl.c:1082)')))“.
requests / urllib3 (Python) — requests.exceptions.SSLError wrapping urllib3 MaxRetryError
Was der Fehler bedeutet
requests hat bei urllib3 eine Verbindung angefordert, im TLS-Handshake ist die Zertifikatsprüfung fehlgeschlagen, und urllib3 hat aufgegeben (bei der Standardeinstellung von requests ohne Wiederholungen schon nach dem ersten Versuch). Der aufschlussreiche Teil steht in den Klammern: Hier konnte OpenSSL den Aussteller nicht in dem CA-Bundle finden, das requests verwendet – standardmäßig ist das die Kopie der Mozilla-Root-Zertifikate aus certifi, nicht der Zertifikatsspeicher des Betriebssystems.
Warum der Fehler auftritt
requests prüft mit certifi, nicht mit dem Zertifikatsspeicher des Systems
häufigEine firmeneigene oder private CA, die mit update-ca-certificates, im Windows-Zertifikatsspeicher oder im macOS-Schlüsselbund installiert wurde, wird ignoriert: requests.certs.where() zeigt auf die cacert.pem von certifi. Genau das zeigt die Reproduktion.
Ein Proxy mit TLS-Inspektion signiert den Datenverkehr neu
häufigAnfragen an öffentliche APIs scheitern nur im Firmennetzwerk, weil das Root-Zertifikat des Proxys nicht in certifi enthalten ist.
Der Server sendet sein Zwischenzertifikat nicht
gelegentlichSendet der Server der API nur sein Leaf-Zertifikat, kann OpenSSL in keinem Bundle ein Root-Zertifikat erreichen; Browser verdecken das, indem sie Zwischenzertifikate nachladen.
REQUESTS_CA_BUNDLE oder CURL_CA_BUNDLE zeigt auf ein unvollständiges Bundle
gelegentlichEin Bundle, das nur das Root-Zertifikat der Firma enthält, lässt alle öffentlichen Hosts scheitern (und umgekehrt); die Variablen ersetzen certifi, statt es zu ergänzen.
So behebst du den Fehler
- 1.
Richtiges Bundle mit verify= übergeben
verify akzeptiert den Pfad zu einer CA-Bundle-Datei oder zu einem gehashten CA-Verzeichnis; Hostnamen- und Datumsprüfungen bleiben dabei aktiv.
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.
REQUESTS_CA_BUNDLE für den Prozess setzen
Zeigt die Variable auf das System-Bundle (nachdem du deine CA dort installiert hast), vertraut jeder Aufruf von requests in diesem Prozess denselben Root-Zertifikaten wie curl und die Tools des Betriebssystems.
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.
Trust Store des Betriebssystems verwenden
Ein Aufruf von truststore.inject_into_ssl() beim Programmstart lässt requests über das Betriebssystem prüfen, sodass von der IT verteilte Root-Zertifikate funktionieren, ohne dass du Bundles pflegen musst.
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.
Eine Session einmal konfigurieren
Setze in Anwendungen Session.verify auf ein Bundle, das die öffentlichen Root-Zertifikate sowie das Root-Zertifikat der Firma enthält, und verwende die Session wieder.
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/")