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:
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ón | Resultado |
|---|---|---|
| 1 | CA privada, cacerts predeterminado del JDK | PKIX path building failed |
| 2 | Raíz importada en una copia de cacerts, el servidor envía la hoja y el intermedio | HTTP 200 |
| 3 | El mismo almacén de confianza, el servidor envía solo la hoja | PKIX path building failed |
| 4 | Igual que 3, -Dcom.sun.security.enableAIAcaIssuers=true | PKIX path building failed |
| 4b | Igual que 4, más -Dcom.sun.security.allowedAIALocations=http://aia.example.test:8080/ | HTTP 200 |
| 5 | El servidor elige el certificado por SNI, el cliente envía SNI | HTTP 200 |
| 6 | El mismo servidor, -Djsse.enableSNIExtension=false | PKIX path building failed |
| 7 | El 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 -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 -cacertsmodifica el almacén de confianza del JDK que ejecutakeytool, y solo ese. - Un almacén de confianza sustituido.
-Djavax.net.ssl.trustStore=only-corp.p12sustituye acacerts, no se suma a él. Un almacén que solo contiene la raíz de la empresa rompe todos los sitios públicos:
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.trustStoreno existe, JDK 25 no falla. Recurre en silencio acacerts, así que tu raíz sencillamente no está. Solo la salida de depuración del gestor de confianza lo indica:
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:
--- 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
SSLSocketFactorypersonalizada 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:
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:
java -Djavax.net.debug=ssl:handshake:verbose -jar app.jar 2>&1 \
| grep -E 'trustStore is|Consuming server Certificate|"subject"|"issuer"|PKIX'
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 -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:
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:
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/:
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
openssl s_client -showcertsdesde el host que falla: ¿la cadena está completa y quién es el emisor superior?-Djavax.net.debug=ssl:handshake:verbose: ¿qué archivo cargó la JVM según la líneatrustStore is:?keytool -listsobre ese archivo: ¿está ahí la raíz emisora?- ¿Hay en medio un proxy que inspecciona TLS? Entonces lo que necesitas es su raíz.
- ¿Se envía SNI? Revisa los literales IP,
jsse.enableSNIExtensiony las fábricas de sockets personalizadas. - ¿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.securityque 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_CERTSdel 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.