Mensaje de error
curl: (60) SSL certificate problem: self-signed certificate in certificate chain
También se muestra como
- curl: (60) SSL certificate problem: self signed certificate in certificate chain
- curl: (60) SSL certificate OpenSSL verify result: self-signed certificate in certificate chain (19)
OpenSSL 3.0 y posteriores imprimen «self-signed»; OpenSSL 1.1.1 imprime «self signed» sin guion, así que ambas grafías aparecen en los registros y en los resultados de búsqueda. Las versiones más recientes de curl (8.22.0 en nuestra prueba) lo formulan como «SSL certificate OpenSSL verify result: self-signed certificate in certificate chain (19)». Las compilaciones de curl con Schannel en Windows notifican este caso como «SEC_E_UNTRUSTED_ROOT».
curl (OpenSSL backend) — CURLE_PEER_FAILED_VERIFICATION, X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN (19)
Qué significa
curl recorrió la cadena que envió el servidor hasta un certificado raíz autofirmado, y esa raíz no está en el bundle de CA de curl. A diferencia de «unable to get local issuer certificate», la cadena está completa: curl ve la raíz, pero no tiene ningún motivo para confiar en ella. En la práctica, la raíz casi siempre pertenece a una CA de la empresa o a un proxy que intercepta TLS.
Por qué ocurre
Un proxy o un antivirus que inspecciona TLS vuelve a firmar las conexiones
frecuenteZscaler, Netskope, Fortinet, Palo Alto y muchos antivirus terminan TLS y presentan certificados emitidos por su propia raíz, que envían en la cadena. El navegador confía en esa raíz a través del sistema operativo, pero curl dentro de WSL, Docker, Git Bash o un runner de CI lee su propio bundle y la rechaza. El emisor del certificado nombra al fabricante del proxy o a tu empresa, no a la CA real del sitio.
El servicio usa una raíz privada y la envía en la cadena
frecuenteLos servicios internos, los ingress de Kubernetes y los dispositivos dedicados suelen usar un certificado de una raíz de la empresa o creada a mano, e incluyen esa raíz en la cadena servida. Enviar la raíz no la convierte en una raíz de confianza: el ancla de confianza ya tiene que estar en el cliente.
La raíz privada está instalada en otro almacén
ocasionalLa raíz se añadió al almacén de certificados de Windows, al llavero de macOS o al cacerts de Java, pero este curl lee un bundle de OpenSSL como /etc/ssl/certs/ca-certificates.crt o la copia que incluyen Git for Windows, Homebrew o conda.
El almacén raíz público del cliente está desactualizado
raraEn raras ocasiones, un servidor envía una raíz pública más reciente que un cliente antiguo y nunca actualizado no tiene. Actualizar el paquete ca-certificates lo soluciona; si la raíz es realmente pública, otros equipos verificarán el sitio sin problemas.
Cómo solucionarlo
- 1.
Averiguar de quién es la raíz
Mira el último certificado de la cadena. Si el sujeto es un fabricante de proxies o de productos de seguridad, o el nombre de tu empresa, la solución es confiar en esa raíz en este equipo. Si es un nombre inesperado en una red que no controlas, detente: alguien está interceptando la conexión.
bash # Print the subject and issuer of every certificate the server (or the proxy) sends openssl s_client -connect api.example.com:443 -servername api.example.com -showcerts </dev/null 2>/dev/null \ | grep -E '^ *[0-9]+ s:|^ *i:' # The last entry has s: == i: — that self-signed root is what curl does not trust. - 2.
Añadir la raíz de la empresa o del proxy al almacén del sistema
Obtén el certificado raíz de tu departamento de TI (o expórtalo del almacén de confianza de un equipo gestionado) e instálalo en el bundle del sistema operativo que usa curl. Los contenedores lo necesitan al construir la imagen; WSL lo necesita dentro de la distribución Linux, no solo en Windows.
bash # Debian, Ubuntu, Alpine sudo cp corp-root-ca.pem /usr/local/share/ca-certificates/corp-root-ca.crt sudo update-ca-certificates # RHEL, Fedora, Amazon Linux sudo cp corp-root-ca.pem /etc/pki/ca-trust/source/anchors/ sudo update-ca-trust - 3.
Confiar en la raíz para un solo comando o un solo entorno
--cacert y CURL_CA_BUNDLE sustituyen el bundle predeterminado de curl, así que, si el mismo proceso también se comunica con sitios públicos, apúntalos a un archivo que contenga tanto las raíces públicas como la privada.
bash curl --cacert corp-root-ca.pem https://api.example.com/ # Keep public roots too: build one bundle with both cat /etc/ssl/certs/ca-certificates.crt corp-root-ca.pem > /opt/certs/bundle.pem export CURL_CA_BUNDLE=/opt/certs/bundle.pem - 4.
Propietarios del servidor: no depender del envío de la raíz
Sirve el certificado hoja y los intermedios; la raíz privada, en cambio, distribúyela a los clientes mediante la gestión de la configuración. Incluir la raíz desperdicia bytes y nunca convierte en confiable una raíz que no lo es.
bash # Serve leaf + intermediates only; clients must already have the root cat api.example.com.crt intermediate.crt > fullchain.pem - 5.
Diagnóstico temporal: ver el emisor con la verificación desactivada
-k desactiva las comprobaciones de certificados para que la salida detallada pueda mostrar el sujeto y el emisor que presenta el servidor. Úsalo solo para identificar la raíz. Si se queda en los scripts, -k permite que cualquiera en la ruta lea y modifique el tráfico, incluidos los tokens y los instaladores descargados.
bash # Diagnostic only: shows who issued the certificate even though curl distrusts it curl -sv -k -o /dev/null https://api.example.com/ 2>&1 | grep -E 'subject:|issuer:'
Errores relacionados
curl: (60) SSL certificate problem: unable to get local issuer certificatecurl (OpenSSL backend)curl: (60) SSL certificate problem: self-signed certificatecurl (OpenSSL backend)verify error:num=19:self-signed certificate in certificate chainOpenSSL CLI (openssl s_client / openssl verify)self-signed certificate in certificate chainNode.js tls / https / fetch[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certifi…Python ssl module (OpenSSL)