Fehlermeldung
PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
Auch ausgegeben als
- javax.net.ssl.SSLHandshakeException: (certificate_unknown) PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
- javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
- unable to find valid certification path to requested target
JDK 21 und 25 stellen den TLS-Alert voran: „javax.net.ssl.SSLHandshakeException: (certificate_unknown) PKIX path building failed: …“. Logs älterer JDKs zeigen „javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: …“. Frameworks und Build-Tools (Spring, Maven, Gradle, IDEs) verpacken den Fehler in eigene Meldungen, behalten den PKIX-Text aber in der verursachenden Exception.
Java (JSSE, SunX509 trust manager) — javax.net.ssl.SSLHandshakeException caused by sun.security.validator.ValidatorException
Was der Fehler bedeutet
Beim PKIX-Pfadaufbau konnte Java vom Zertifikat des Servers keine Kette zu einem Vertrauensanker im Trust Store bilden, den die JVM verwendet. „Requested target“ ist das Zertifikat des Servers. Java vertraut nur seiner eigenen Datei cacerts (oder dem mit javax.net.ssl.trustStore angegebenen Store), nicht dem des Betriebssystems – deshalb scheitert hier ein Zertifikat von einer Firmen-CA, von einem Proxy mit TLS-Inspektion oder von einem Server ohne sein Zwischenzertifikat.
Warum der Fehler auftritt
Das Zertifikat des Servers stammt von einer privaten CA
häufigInterne Dienste, Kubernetes-Ingresses, Nexus- oder Artifactory-Repositorys und Datenbanken mit Firmenzertifikaten haben Ketten, die bei einem Root-Zertifikat enden, das nicht in der Datei cacerts des JDK steht.
Ein Proxy mit TLS-Inspektion signiert die Verbindung neu
häufigIn Firmennetzen erscheinen Maven Central, Gradle-Plugin-Portale und öffentliche APIs mit Zertifikaten, die unter dem Root-Zertifikat des Proxys ausgestellt sind. Wird dieses Root-Zertifikat unter Windows oder macOS installiert, kommt es bei Java nicht an.
Der Server sendet sein Zwischenzertifikat nicht
häufigJava lädt fehlende Zwischenzertifikate standardmäßig nicht nach (com.sun.security.enableAIAcaIssuers ist ausgeschaltet), deshalb scheitert ein Server, der nur mit seinem Leaf-Zertifikat konfiguriert ist, obwohl Browser ihn akzeptieren.
Die JVM verwendet einen anderen Trust Store, als du denkst
gelegentlichIDEs und Build-Tools bringen ihr eigenes JDK mit, Anwendungsserver setzen javax.net.ssl.trustStore auf einen Store mit nur wenigen CAs, und Container verwenden womöglich eine JRE, deren cacerts nie aktualisiert wurde. Ein Root-Zertifikat, das in die Datei cacerts eines JDK importiert wurde, hilft einem anderen JDK nicht.
Einem sehr alten JDK fehlen neuere öffentliche Root-Zertifikate
seltenUngepatchte Builds von Java 8 aus der Zeit, bevor das Root-Zertifikat einer CA in cacerts aufgenommen wurde, scheitern bei Websites, die dieses Root-Zertifikat verwenden; aktuelle Updates enthalten es.
So behebst du den Fehler
- 1.
Kette und verwendeten Trust Store ansehen
openssl s_client zeigt, was der Server sendet. Mit javax.net.debug=ssl:handshake:verbose gibt Java den Pfad des geladenen Trust Stores und jedes empfangene Zertifikat aus.
bash openssl s_client -connect api.example.com:443 -servername api.example.com -showcerts </dev/null 2>/dev/null \ | grep -E '^ *[0-9]+ s:|^ *i:' # Or from Java itself: prints the chain and the trust store in use java -Djavax.net.debug=ssl:handshake:verbose -jar app.jar 2>&1 | grep -E 'trustStore is|Subject:|Issuer:' - 2.
Anwendungseigenen Trust Store mit deinem Root-Zertifikat verwenden
Kopiere die Datei cacerts des JDK (damit öffentliche CAs weiter funktionieren), importiere das Root-Zertifikat der Firma oder des Proxys mit keytool und verweise die Anwendung mit javax.net.ssl.trustStore und trustStorePassword darauf (oder über JAVA_TOOL_OPTIONS bei Tools, die du nicht selbst startest).
bash # A copy of the JDK's cacerts plus your root: public sites keep working 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 - 3.
Root-Zertifikat in die Datei cacerts des JDK importieren
keytool -cacerts schreibt in den Standard-Store des JDK, unter dem keytool läuft (Passwort standardmäßig „changeit“). Jede Anwendung auf diesem JDK vertraut dann dem Root-Zertifikat; die Änderung geht verloren, wenn das JDK ersetzt wird.
bash # Add the root to this JDK's default trust store (applies to every app on this JDK; # repeat after each JDK upgrade) sudo keytool -importcert -noprompt -cacerts -alias corp-root -file corp-root-ca.pem -storepass changeit - 4.
Container: CA vom Image importieren lassen
Ist USE_SYSTEM_CA_CERTS gesetzt, fügt der Entrypoint der Images von Eclipse Temurin beim Start die unter /certificates eingehängten Zertifikatsdateien *.crt zur Datei cacerts der JVM hinzu (das geschieht nur mit dem Standard-Entrypoint des Images). Andere Images brauchen den keytool-Schritt im Dockerfile.
bash # Eclipse Temurin images: mount your CA(s) as *.crt and let the entrypoint add them to cacerts docker run -e USE_SYSTEM_CA_CERTS=1 \ -v "$PWD/certs:/certificates/" eclipse-temurin:25.0.4.1_1-jdk-noble java -jar /app/app.jar - 5.
Einem einzelnen Client einen eigenen Trust Store geben
Baue einen SSLContext aus einer TrustManagerFactory, die mit deinem Trust Store initialisiert ist, und übergib ihn an HttpClient (oder OkHttp, Apache HttpClient, Netty). Installiere nie einen Trust Manager, der jedes Zertifikat akzeptiert.
java // One client with its own trust store (verification stays on) 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();
Verwandte Fehler
PKIX path validation failed: java.security.cert.CertPathValidatorException: val…Java (JSSE)java.security.InvalidAlgorithmParameterException: the trustAnchors parameter mu…Java (JSSE / PKIX)No subject alternative DNS name matching api.example.com found.Java (JSSE HostnameChecker)curl: (60) SSL certificate problem: unable to get local issuer certificatecurl (OpenSSL backend)x509: certificate signed by unknown authorityGo crypto/x509