«unable to get local issuer certificate»: encontrar y corregir el intermedio que falta

La página carga sin problemas en el navegador, pero curl, un script o una aplicación Java rechazan la conexión. En la mayoría de los casos falta un certificado intermedio en el servidor.

Mensajes típicos

curl: (60) SSL certificate problem: unable to get local issuer certificate
git:  SSL certificate problem: unable to get local issuer certificate
Python: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate
Node:   Error: unable to get local issuer certificate (UNABLE_TO_GET_ISSUER_CERT_LOCALLY)
Java:   PKIX path building failed: unable to find valid certification path to requested target

Todos significan lo mismo: partiendo del certificado del servidor, el cliente no consigue formar una cadena hasta una CA raíz de confianza.

Los navegadores son indulgentes. Si falta un intermedio, lo descargan mediante la dirección AIA del certificado o lo toman de su caché porque lo vieron en otra web. curl, Python, Java, Git y la mayoría de las apps no lo hacen. Que una web funcione en el navegador no demuestra, por tanto, que la cadena esté completa.

Tres causas posibles

1. El servidor no envía la cadena completa (la causa más habitual)

Solo se configuró el certificado del servidor, sin el intermedio. Compruébalo con el comprobador SSL o en la línea de comandos:

openssl s_client -connect www.ejemplo.es:443 -servername www.ejemplo.es -showcerts </dev/null

Si la salida muestra un solo certificado (0 s:CN=www.ejemplo.es) junto con verify error:num=20:unable to get local issuer certificate, falta el intermedio.

2. Al cliente le falta la CA raíz

Imágenes Docker antiguas sin el paquete ca-certificates, una instalación de Java desactualizada o un paquete certifi viejo en Python no conocen las CA raíz más recientes.

3. Un cortafuegos intercepta TLS o se trata de una CA interna

Muchos proxies y cortafuegos de empresa sustituyen los certificados por los suyos, emitidos por una CA interna. Los navegadores de los equipos de la empresa conocen esa CA por directiva de grupo, pero los contenedores Docker, Python y Java no. Lo mismo vale para servidores con certificados de tu propia CA corporativa.

Solución en el servidor: enviar la cadena completa

# nginx: certificado + intermedio en un archivo
ssl_certificate     /etc/ssl/ejemplo/fullchain.pem;
ssl_certificate_key /etc/ssl/ejemplo/server.key;

# Apache 2.4.8+: la cadena va en SSLCertificateFile
SSLCertificateFile    /etc/ssl/ejemplo/fullchain.pem
SSLCertificateKeyFile /etc/ssl/ejemplo/server.key

# Let's Encrypt: usar fullchain.pem, no cert.pem

En Windows/IIS, el intermedio tiene que estar en el almacén «Entidades de certificación intermedias» del equipo. Lo más fácil es importar un PFX que contenga la cadena. Si no tienes el intermedio, el conversor de certificados completa la cadena mediante AIA, y el comprobador SSL se la pasa directamente con «Completar la cadena en el conversor».

Solución en el cliente

# Debian/Ubuntu: actualizar CA raíz o añadir tu propia CA
apt install ca-certificates
cp raiz-empresa.crt /usr/local/share/ca-certificates/ && update-ca-certificates

# Python (requests)
pip install --upgrade certifi
export REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt

# Java 9+
keytool -importcert -cacerts -alias raiz-empresa -file raiz-empresa.crt

No uses curl -k, verify=False ni NODE_TLS_REJECT_UNAUTHORIZED=0 como solución permanente. La conexión sigue cifrada, pero ya no está protegida frente a atacantes.

FAQ

¿Tengo que enviar la CA raíz?

No. El servidor envía el certificado del servidor y los intermedios. La CA raíz ya tiene que estar en el cliente; una raíz enviada se ignora.

¿Cómo sé si el problema está en el servidor o en el cliente?

Comprueba el servidor con el comprobador SSL. Si indica una cadena completa, el problema está en el cliente (raíz que falta o proxy corporativo). Si avisa de un intermedio que falta, hay que corregir el servidor.

¿Por qué el error solo aparece en Docker?

Las imágenes ligeras como alpine o debian-slim a menudo no incluyen CA raíz. Instala el paquete ca-certificates en el Dockerfile o copia tu CA corporativa y ejecuta update-ca-certificates.