Mensaje de error
[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate
También se muestra como
- ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate
Python añade la ubicación en el código fuente C, por ejemplo «(_ssl.c:1082)»; el número cambia con la versión de Python y no significa nada para el diagnóstico. urllib envuelve el error como «<urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] …>», y requests y urllib3, como «Max retries exceeded … (Caused by SSLError(…))».
Python ssl module (OpenSSL) — ssl.SSLCertVerificationError, X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY (20)
Qué significa
El módulo ssl de Python pidió a OpenSSL que verificara el certificado del servidor, y OpenSSL no encontró el emisor del último certificado de la cadena entre los certificados de CA que cargó este SSLContext. El servidor no está necesariamente mal configurado: a menudo, este Python simplemente consulta un bundle de CA distinto (o vacío) del que usan tu navegador o tu sistema operativo.
Por qué ocurre
Python de python.org en macOS sin sus certificados
frecuenteEl instalador de python.org para macOS incluye su propio OpenSSL, que no lee el llavero de macOS. Hasta que se ejecuta «Install Certificates.command» para esa versión de Python, no tiene ningún certificado de CA, así que todas las solicitudes HTTPS fallan de esta manera.
Una CA privada o un proxy que inspecciona TLS
frecuenteLos servicios de la empresa y los proxies corporativos usan certificados cuya raíz se distribuye a los equipos gestionados a través del almacén del sistema operativo. El OpenSSL de Python no consulta el almacén de Windows ni el de macOS, y los contenedores Linux no tienen la raíz salvo que se añada.
El servidor no envía su certificado intermedio
frecuenteOpenSSL no descarga los intermedios que faltan. Si el servidor solo envía su certificado hoja, Python falla mientras que los navegadores, que guardan en caché y descargan los intermedios, parecen funcionar bien.
La CA se añadió al almacén del sistema, pero la biblioteca usa certifi
ocasionalrequests, httpx, aiohttp, botocore y pip (con la gestión de certificados heredada) verifican con el bundle de certifi, que se distribuye como paquete de Python. Una raíz instalada con update-ca-certificates o en el llavero del sistema operativo es invisible para ellos.
SSL_CERT_FILE o SSL_CERT_DIR apunta al lugar equivocado
raraEstas variables sustituyen las ubicaciones predeterminadas de OpenSSL. Una ruta que quedó de otra herramienta, o un bundle que solo contiene una raíz privada, hace que fallen los sitios públicos (o al revés).
Cómo solucionarlo
- 1.
Compilaciones de python.org para macOS: ejecutar Install Certificates.command
El script instala certifi y lo enlaza como bundle del OpenSSL de ese Python. Hay que volver a ejecutarlo después de instalar otra versión de Python.
bash # macOS, python.org installer: run once per installed Python version "/Applications/Python 3.14/Install Certificates.command" - 2.
Usar el almacén de confianza del sistema operativo con truststore
truststore hace que ssl.SSLContext verifique a través del sistema operativo (el llavero en macOS, el almacén de certificados de Windows, el bundle del sistema en Linux), de modo que las raíces que despliega tu departamento de TI también funcionan en Python. También cubre urllib3, requests y pip, que lo usa de forma predeterminada desde 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.
Cargar la CA explícitamente para un cliente
Pasa cafile (o cadata) a ssl.create_default_context() en el código que se comunica con un servicio privado. El contexto sigue verificando los nombres de host y las fechas.
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.
Comprobar y establecer la ruta del bundle predeterminado
ssl.get_default_verify_paths() muestra qué archivo y qué directorio usa OpenSSL. SSL_CERT_FILE y SSL_CERT_DIR los sustituyen para el proceso; haz que apunten a un bundle que contenga tanto las raíces públicas como tu 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.
Propietarios de servidores: enviar la cadena completa
Si el handshake contiene un solo certificado y su emisor es un intermedio público, configura el servidor con el archivo de cadena completa.
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'
Errores relacionados
<urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unab…Python urllib.requestMax retries exceeded with url: / (Caused by SSLError(SSLCertVerificationError(1…requests / urllib3 (Python)There was a problem confirming the ssl certificate: [SSL: CERTIFICATE_VERIFY_FA…pip[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certifi…Python ssl module (OpenSSL)curl: (60) SSL certificate problem: unable to get local issuer certificatecurl (OpenSSL backend)