Saltar al contenido
ecdsa.com
← Todos los artículos

PKIX path building failed en Java: causas y soluciones

Java no confía en lo mismo que tu navegador. Usa su propio almacén de confianza, no descarga por defecto los intermedios que faltan y muestra el mismo mensaje ante cualquier fallo. Cada una de las causas siguientes se reprodujo en JDK 25, junto con el comando que te indica cuál es la tuya.

Respuesta breve. PKIX path building failed: ... unable to find valid certification path to requested target significa que la JVM no pudo construir una cadena desde el certificado del servidor hasta ninguna raíz de confianza del almacén de confianza que realmente cargó. Suele ser $JAVA_HOME/lib/security/cacerts, no el almacén del sistema operativo. Las causas habituales son una CA privada o de un proxy que no está en ese archivo, un servidor que no envía su certificado intermedio, una JVM que usa un almacén de confianza distinto del que crees, un JDK antiguo sin una raíz más reciente y un nombre SNI que no se envía. Averigua cuál es con openssl s_client -showcerts y -Djavax.net.debug=ssl:handshake:verbose. Después corrige el almacén de confianza o el servidor. Nunca instales un gestor de confianza que lo acepte todo.

Qué significa el mensaje

En los JDK actuales, la excepción completa tiene este aspecto. JDK 21 y 25 anteponen el nombre de la alerta TLS:

JDK 25.0.4.1, HttpClient contra un servidor con una CA privada
javax.net.ssl.SSLHandshakeException: (certificate_unknown) PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

Sus partes:

  • PKIX es el modelo de rutas de certificación X.509 de RFC 5280. La implementación de Java está en sun.security.provider.certpath.
  • Path building (la construcción de la ruta) es el primero de dos pasos. Java intenta encontrar alguna secuencia de certificados que vaya desde la hoja del servidor hasta un ancla de confianza. Solo si lo consigue, la validación de la ruta (path validation) comprueba las fechas, el uso de clave, las restricciones y la revocación. Los fallos del segundo paso tienen sus propios mensajes, por ejemplo PKIX path validation failed: validity check failed.
  • «requested target» es el certificado del servidor.

Los frameworks envuelven esta excepción, por ejemplo la ResourceAccessException de Spring, el «Could not transfer artifact» de Maven o el «Could not GET» de Gradle. El texto PKIX sigue en la cadena de causas, así que búscalo en la traza de pila completa.

Las cinco causas

Las cinco se reprodujeron con eclipse-temurin:25.0.4.1_1-jdk-noble y un pequeño programa con java.net.http.HttpClient. La PKI de prueba tiene una raíz, un certificado intermedio («Issuing CA 1») y un certificado hoja para api.example.test.

#SituaciónResultado
1CA privada, cacerts predeterminado del JDKPKIX path building failed
2Raíz importada en una copia de cacerts, el servidor envía la hoja y el intermedioHTTP 200
3El mismo almacén de confianza, el servidor envía solo la hojaPKIX path building failed
4Igual que 3, -Dcom.sun.security.enableAIAcaIssuers=truePKIX path building failed
4bIgual que 4, más -Dcom.sun.security.allowedAIALocations=http://aia.example.test:8080/HTTP 200
5El servidor elige el certificado por SNI, el cliente envía SNIHTTP 200
6El mismo servidor, -Djsse.enableSNIExtension=falsePKIX path building failed
7El mismo servidor, URL con una dirección IP (sin SNI)PKIX path building failed

1. El certificado procede de una CA en la que Java no confía

Es el caso más frecuente: servicios internos, ingress de Kubernetes, Nexus o Artifactory con certificados de la empresa, bases de datos y cualquier sitio visto a través de un proxy que inspecciona TLS, que vuelve a firmar el tráfico con su propia raíz. Instalar esa raíz en Windows o macOS deja contentos a los navegadores, pero en la mayoría de las plataformas Java no lee el almacén del sistema operativo. El cacerts del JDK (118 entradas en esta compilación de Temurin 25) no tiene ni idea de quién es «Example Corp Root CA».

2. El servidor no envía su certificado intermedio

Un servidor debe enviar su certificado hoja y los intermedios hasta la raíz, sin incluirla. Muchos servidores están configurados solo con la hoja. Los navegadores suelen ocultar el error, porque guardan los intermedios en caché o los descargan de la URL que figura en la extensión Authority Information Access (AIA) del certificado. Java no hace ninguna de las dos cosas por defecto. Las filas 3 y 4 de la tabla corresponden a esta situación. La raíz es de confianza, pero la ruta no se puede construir porque Java nunca ve «Issuing CA 1».

Puedes ver en dos líneas lo que envía un servidor:

openssl s_client -showcerts: el servidor del puerto 8443 envía la cadena; el del 8444, solo la hoja
$ openssl s_client -connect api.example.test:8443 -servername api.example.test -showcerts </dev/null 2>/dev/null | grep -E '^ *[0-9]+ s:|^ *i:'
 0 s:CN = api.example.test
   i:CN = Example Corp Issuing CA 1
 1 s:CN = Example Corp Issuing CA 1
   i:CN = Example Corp Root CA
$ openssl s_client -connect api.example.test:8444 -servername api.example.test -showcerts </dev/null 2>/dev/null | grep -E '^ *[0-9]+ s:|^ *i:'
 0 s:CN = api.example.test
   i:CN = Example Corp Issuing CA 1

Si la última línea i: nombra algo distinto de una raíz en la que confías, falta un intermedio. El constructor de cadena de certificados toma un paquete pegado, lo ordena, descarga por AIA los intermedios que faltan y te da la cadena exacta que debería enviar el servidor.

3. La JVM usa un almacén de confianza distinto del que crees

La raíz está importada y aun así falla. Motivos habituales:

  • Otro JDK. Los IDE, las toolchains de Gradle, los wrappers de Maven, los servidores de aplicaciones y las imágenes de Docker traen su propio JDK. keytool -cacerts modifica el almacén de confianza del JDK que ejecuta keytool, y solo ese.
  • Un almacén de confianza sustituido. -Djavax.net.ssl.trustStore=only-corp.p12 sustituye a cacerts, no se suma a él. Un almacén que solo contiene la raíz de la empresa rompe todos los sitios públicos:
JDK 25: almacén de confianza solo con la raíz de la empresa, conexión a letsencrypt.org
javax.net.ssl.SSLHandshakeException: (certificate_unknown) PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
  • Una errata en la ruta del almacén de confianza. Si el archivo de javax.net.ssl.trustStore no existe, JDK 25 no falla. Recurre en silencio a cacerts, así que tu raíz sencillamente no está. Solo la salida de depuración del gestor de confianza lo indica:
JDK 25: -Djavax.net.ssl.trustStore=/tmp/app-trustore.p12 (una errata), -Djavax.net.debug=ssl:trustmanager
Inaccessible trust store: /tmp/app-trustore.p12
trustStore is: /opt/java/openjdk/lib/security/cacerts
Fatal (CERTIFICATE_UNKNOWN): PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target (

Un almacén que existe pero no contiene ningún certificado de confianza falla de otra manera, con the trustAnchors parameter must be non-empty.

  • Un almacén a nivel de framework. Los SSL bundles de Spring Boot, los conectores de Tomcat, Kafka y los drivers de bases de datos tienen su propia configuración de almacén de confianza, y en sus conexiones tiene prioridad sobre los valores predeterminados de la JVM.

La primera línea de la salida de depuración de JSSE aclara qué archivo se cargó (consulta el diagnóstico).

4. El JDK es demasiado antiguo para la raíz del servidor

Cada actualización del JDK incluye una instantánea de las raíces de confianza. Un sitio público cuya cadena termina en una raíz más reciente falla en un JDK anterior a esa raíz. La página de compatibilidad de Let's Encrypt da un ejemplo: ISRG Root X2 es de confianza a partir de Java 8u401, 11.0.22, 17.0.10 y 21.0.2. Eliminar dos raíces de una copia de cacerts muestra lo que hace un almacén desactualizado:

JDK 25 con ISRG Root X1 y X2 eliminadas de una copia de cacerts
--- letsencrypt.org (chains to ISRG roots)
javax.net.ssl.SSLHandshakeException: (certificate_unknown) PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
--- www.google.com (different CA)
HTTP 200

Si solo fallan algunos sitios públicos, mira la raíz en la que termina su cadena y después el nivel de actualización de tu JDK. Lo mismo le ocurre a un almacén de confianza que se copió de cacerts hace años y nunca se actualizó.

5. Sin SNI, el servidor envía otro certificado

Un servidor que aloja varios nombres elige el certificado según la indicación del nombre del servidor (SNI) que llega en el ClientHello. Sin SNI envía un certificado predeterminado, que en la prueba era un default.invalid autofirmado. Ese certificado no se encadena a nada, así que el error vuelve a ser PKIX (filas 6 y 7). Java no envía SNI cuando:

  • la URL usa una dirección IP (TLS prohíbe los literales IP en SNI),
  • está definido -Djsse.enableSNIExtension=false, a menudo heredado de una solución provisional antigua,
  • una SSLSocketFactory personalizada crea sockets sin nombre de host.

openssl s_client sin -servername muestra lo que recibe un cliente así.

Diagnóstico paso a paso

1. Mira la cadena que envía el servidor, desde el equipo que falla si puedes. Un proxy puede servir a ese equipo un certificado distinto:

Qué envía el servidor y quién firmó cada certificado
openssl s_client -connect host.example:443 -servername host.example -showcerts </dev/null 2>/dev/null \
  | grep -E '^ *[0-9]+ s:|^ *i:'

A través de un proxy de inspección, el emisor superior es la CA del proxy, no una pública. Eso basta para explicar el error.

2. Pregunta a Java qué almacén de confianza cargó y qué recibió. La salida de depuración es larga, unas 5000 líneas para un solo handshake fallido en nuestra prueba, así que fíltrala:

Depuración de JSSE, filtrada
java -Djavax.net.debug=ssl:handshake:verbose -jar app.jar 2>&1 \
  | grep -E 'trustStore is|Consuming server Certificate|"subject"|"issuer"|PKIX'
Líneas clave de la salida de depuración de JSSE (JDK 25, CA privada, extracto)
trustStore is: /opt/java/openjdk/lib/security/cacerts
...
javax.net.ssl ... Consuming server Certificate handshake message (
"issuer"             : "CN=Example Corp Issuing CA 1",
"subject"            : "CN=api.example.test",
...
"issuer"             : "CN=Example Corp Root CA",
"subject"            : "CN=Example Corp Issuing CA 1",
...
javax.net.ssl ... Fatal (CERTIFICATE_UNKNOWN): PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target (

La primera línea nombra el archivo del almacén de confianza. Los bloques de certificado muestran la cadena que envía el servidor, empezando por la hoja; el emisor del último bloque es el certificado que Java no pudo enlazar con una raíz. Si ese emisor es una raíz o un intermedio en el que esperabas confiar, comprueba ese archivo exacto:

keytool: ¿está la raíz en el almacén que cargó la JVM?
$ keytool -list -cacerts -storepass changeit -alias corp-root
keytool error: java.lang.Exception: Alias <corp-root> does not exist

Para otro archivo, usa keytool -list -keystore /path/to/store.p12. -v imprime el sujeto y la huella digital de cada entrada, así que puedes cotejar el certificado en lugar del alias.

3. Para ver los detalles de la construcción de la ruta, añade -Djava.security.debug=certpath. Registra cada emisor candidato que consideró el constructor de rutas y, en el caso de AIA, si se permitió la descarga.

Soluciones que mantienen la verificación activada

Corrige primero el servidor cuando falta un intermedio. Configura la cadena completa (la hoja y después los intermedios) en nginx, Apache, el balanceador de carga o el ingress. Se benefician todos los clientes, no solo Java.

Usa un almacén de confianza de la aplicación que añada tu raíz cuando la CA es privada o un proxy vuelve a firmar el tráfico. Parte de una copia de cacerts para que los sitios públicos sigan funcionando:

Un almacén de confianza de la aplicación: las raíces del JDK más la raíz de la empresa
cp "$JAVA_HOME/lib/security/cacerts" app-truststore.p12
keytool -importcert -noprompt -alias corp-root -file corp-root-ca.pem \
  -keystore app-truststore.p12 -storepass changeit
java -Djavax.net.ssl.trustStore=app-truststore.p12 -Djavax.net.ssl.trustStorePassword=changeit -jar app.jar

Para las herramientas que no arrancas tú, como Maven, Gradle o las pruebas que ejecuta el IDE, pasa las mismas propiedades mediante JAVA_TOOL_OPTIONS o las opciones de JVM de la propia herramienta. keytool -importcert -cacerts, en cambio, modifica el almacén predeterminado del JDK. Así el cambio llega a todas las aplicaciones de ese JDK, pero desaparece con la siguiente actualización del JDK.

Da a un cliente su propio almacén de confianza cuando solo una integración necesita la raíz adicional. Esta versión se ejecutó contra el servicio privado y contra un sitio público:

Java: un SSLContext para un único HttpClient
KeyStore ts = KeyStore.getInstance("PKCS12");
try (InputStream in = Files.newInputStream(Path.of("app-truststore.p12"))) {
    ts.load(in, "changeit".toCharArray());
}
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ts);
SSLContext ssl = SSLContext.getInstance("TLS");
ssl.init(null, tmf.getTrustManagers(), null);
HttpClient client = HttpClient.newBuilder().sslContext(ssl).build();
// https://api.example.test:8443/ -> HTTP 200
// https://letsencrypt.org/ -> HTTP 200

OkHttp, Apache HttpClient, Netty, gRPC y los drivers JDBC aceptan todos un SSLContext o un TrustManager construido de la misma forma.

En contenedores, las imágenes de Eclipse Temurin importan CA adicionales al arrancar cuando USE_SYSTEM_CA_CERTS está definida y hay archivos *.crt montados en /certificates/:

docker run -e USE_SYSTEM_CA_CERTS=1 -v ./certs:/certificates/ eclipse-temurin:25.0.4.1_1-jdk-noble (extracto)
Adding certificate with alias Example Corp Root CA to the JVM truststore
Your keystore contains 240 entries

Esto solo se ejecuta a través del entrypoint predeterminado de la imagen. Con otras imágenes base, ejecuta keytool -importcert en el Dockerfile.

En macOS con JDK 23 o posterior, -Djavax.net.ssl.trustStoreType=KeychainStore-ROOT hace que Java use el llavero de raíces del sistema, de modo que una raíz instalada por tu departamento de TI pasa a ser de confianza (JDK-8320362). En Windows, -Djavax.net.ssl.trustStoreType=Windows-ROOT hace lo mismo con el almacén raíz de Windows.

Actualiza el JDK cuando falta una raíz pública. Parchear cacerts a mano en un JDK antiguo deja sin aplicar el resto de sus correcciones de seguridad.

Por qué «confiar en todos los certificados» no es una solución

La respuesta más popular en internet es un X509TrustManager cuyo checkServerTrusted no hace nada, a menudo acompañado de un HostnameVerifier que devuelve true. Hace desaparecer la excepción porque convierte TLS en un cifrado sin verificar quién está al otro lado. Cualquiera que pueda interceptar la conexión puede presentar cualquier certificado, leer el tráfico y modificarlo: una red Wi-Fi hostil, un router comprometido, un proxy mal configurado, un secuestro de DNS. Además, el código suele sobrevivir a su propósito «temporal» y acaba copiado en el siguiente proyecto. Cada una de las soluciones anteriores lleva unos minutos y mantiene la verificación activada.

Lo mismo vale para importar el certificado del propio servidor como ancla de confianza. Funciona hasta la siguiente renovación, que con una vida útil de los certificados más corta llega en cuestión de semanas. Confía en la CA que emite los certificados, no en el certificado.

Lista de comprobación

  1. openssl s_client -showcerts desde el host que falla: ¿la cadena está completa y quién es el emisor superior?
  2. -Djavax.net.debug=ssl:handshake:verbose: ¿qué archivo cargó la JVM según la línea trustStore is:?
  3. keytool -list sobre ese archivo: ¿está ahí la raíz emisora?
  4. ¿Hay en medio un proxy que inspecciona TLS? Entonces lo que necesitas es su raíz.
  5. ¿Se envía SNI? Revisa los literales IP, jsse.enableSNIExtension y las fábricas de sockets personalizadas.
  6. ¿Es el JDK lo bastante reciente para la raíz del sitio?

Fuentes

  • RFC 5280: validación de la ruta de certificación (§6).
  • Oracle, notas de la versión de JDK 21.0.10: reglas de filtrado para los URI de la extensión Authority Info Access (com.sun.security.allowedAIALocations).
  • El archivo java.security que incluye JDK 25 ($JAVA_HOME/conf/security/java.security), sección «X.509 AuthorityInfoAccess caIssuer URI Filtering».
  • JDK-8320362: carga de certificados ancla desde el almacén de claves del llavero (KeychainStore-ROOT, JDK 23).
  • Let's Encrypt, Compatibilidad de certificados.
  • Imágenes de contenedor de Eclipse Temurin: la opción USE_SYSTEM_CA_CERTS del entrypoint.

Todos los resultados de esta página se obtuvieron el 20 de octubre de 2026 en eclipse-temurin:25.0.4.1_1-jdk-noble (OpenJDK 25.0.4.1 y OpenSSL 3.0.13 para los servidores de prueba), con certificados de prueba generados en el contenedor.