How-to guide · Trust a CA
How to add a CA to a Java trust store (cacerts)
In one sentence
Your Java program trusts your private root CA, either through the JDK's cacerts or through its own trust store, and you have proved it with a real connection.
- Task
- Trust a CA
- Platforms
- Java
- Linux
- Windows
- macOS
- Docker
- Level
- Practitioner · 4 steps
On this page
Before you start
- The root certificate of your private CA (PEM or DER). Never a private key.
- Its SHA-256 fingerprint, from a channel you trust.
- Which Java runs the failing program. A machine often has several.
- Write access to that JDK’s folder (
sudoor an administrator terminal) if you change itscacerts.
Why it matters: Java ignores the Windows and macOS stores and uses its own trust store: a keystore file named cacerts in JAVA_HOME/lib/security. A root that is trusted everywhere else still gives PKIX path building failed until Java knows it.
Steps
1. Find the Java that runs your program
java -XshowSettings:properties -version 2>&1 | grep -E 'java.home|java.version ='
java.home = /opt/jdk-25.0.4.1+1
java.version = 25.0.4.1
Run this with the same java your program uses. IDEs ship their own runtime, Gradle toolchains and Maven’s JAVA_HOME may differ, and every container image has its own JDK. Changing the wrong one is the most common reason “it still fails”.
If a file named jssecacerts exists in that JDK’s lib/security folder, Java uses it instead of cacerts (JSSE).
2. Import the root into the JDK’s cacerts
-cacerts (JDK 9 and later) selects the right file and type for you (keytool). Leave out -noprompt the first time, so keytool shows you the fingerprints before it asks:
sudo keytool -importcert -cacerts -alias example-corp-root -file corp-root.crt
Owner: CN=Example Corp Root CA, O=Example Corp, C=BE
Issuer: CN=Example Corp Root CA, O=Example Corp, C=BE
Serial number: 15d0a60c3f02987ab1646cb51bba6b3d07450314
Valid from: Tue Oct 20 03:43:50 CEST 2026 until: Fri Oct 17 03:43:50 CEST 2036
Certificate fingerprints:
SHA1: 3A:54:70:D8:9C:85:77:FE:DC:B8:17:10:DE:F4:23:71:21:4A:F7:08
SHA256: BC:02:A4:F5:DC:68:B3:FD:CD:C4:78:03:0E:30:8C:B5:9D:6D:A0:84:5A:A0:B4:33:36:60:78:8E:96:C2:2E:01
Signature algorithm name: SHA256withECDSA
Subject Public Key Algorithm: 384-bit EC (secp384r1) key
...
BasicConstraints:[
CA:true
PathLen: no limit
]
...
Trust this certificate? [no]: yes
Certificate was added to keystore
Compare the SHA256 line with the published fingerprint before you type yes, and check that Owner and Issuer are the same and CA:true is set.
On the Temurin JDK 25 we used, cacerts is a password-less PKCS #12 file, so keytool did not ask for a password. Where cacerts is a JKS file (JDK 17 and earlier, and Debian and Ubuntu’s OpenJDK), it asks: the initial password is changeit, which administrators should change. In scripts, -storepass changeit -noprompt works with both kinds.
3. Or give one application its own trust store
To leave the JDK untouched, start from a copy of cacerts. Setting javax.net.ssl.trustStore replaces the default store completely:
cp "$JAVA_HOME/lib/security/cacerts" app-truststore.p12
keytool -importcert -keystore app-truststore.p12 -storepass changeit -alias example-corp-root -file corp-root.crt -noprompt
java -Djavax.net.ssl.trustStore=app-truststore.p12 -Djavax.net.ssl.trustStorePassword=changeit -jar app.jar
We checked what happens without the copy: a trust store holding only the private root reached the internal server, but a public site then failed with PKIX path building failed.
To pass the same options to every JVM started from a shell or service, put them in JAVA_TOOL_OPTIONS. For Maven use MAVEN_OPTS; for Gradle, systemProp.javax.net.ssl.trustStore=… lines in gradle.properties.
4. Or let Java use the operating system’s store
- Linux distribution JDKs: on Debian and Ubuntu (with
ca-certificates-java) and on the RHEL family, adding the root withupdate-ca-certificatesorupdate-ca-trustalso updates Java’scacerts. See Linux. - Windows: the SunMSCAPI provider exposes the Windows store as a keystore (Oracle providers).
-Djavax.net.ssl.trustStore=NONE -Djavax.net.ssl.trustStoreType=Windows-ROOTmakes Java trust what Windows trusts; useWindows-ROOT-LOCALMACHINEfor services (JDK 19 and later, and OpenJDK 17.0.5 and 11.0.18 and later). See Windows. Windows downloads some public roots only when a Windows program first needs them, and Java does not trigger that download, so test public sites too. - Eclipse Temurin containers: mount your PEM
.crtfiles at/certificatesand setUSE_SYSTEM_CA_CERTS=1; at container start, the image’s entrypoint adds them to the JVM trust store and the system store, unless you replace that entrypoint (image documentation).
FROM eclipse-temurin:25-jre
ENV USE_SYSTEM_CA_CERTS=1
COPY corp-root.crt /certificates/corp-root.crt
Check it worked
List the entry and compare the fingerprint:
keytool -list -cacerts -alias example-corp-root
example-corp-root, Oct 20, 2026, trustedCertEntry,
Certificate fingerprint (SHA-256): BC:02:A4:F5:DC:68:B3:FD:CD:C4:78:03:0E:30:8C:B5:9D:6D:A0:84:5A:A0:B4:33:36:60:78:8E:96:C2:2E:01
Then make a real connection with the same JDK. Save this as TlsCheck.java:
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class TlsCheck {
public static void main(String[] args) throws Exception {
HttpResponse<Void> r = HttpClient.newHttpClient().send(
HttpRequest.newBuilder(URI.create(args[0])).build(),
HttpResponse.BodyHandlers.discarding());
System.out.println("HTTP " + r.statusCode());
}
}
Java 11 and later run a single source file directly. Against our test server, before and after the import:
java TlsCheck.java https://localhost:18443/
Exception in thread "main" javax.net.ssl.SSLHandshakeException: (certificate_unknown) PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
HTTP 200
Add the -Djavax.net.ssl.trustStore=… options to test an application trust store.
If something goes wrong
- Still
PKIX path building failed. You changed a different JDK (step 1), ajssecacertsfile takes priority, or a JDK update replacedcacertsand your import with it. Prefer an application trust store or the operating system route, which survive updates. - A typo in
javax.net.ssl.trustStore. If the file cannot be read, OpenJDK silently falls back tocacerts(TrustStoreManager), so your store is ignored without an error. Check the path and permissions. keytool error: java.io.FileNotFoundException: …/cacerts (Permission denied). In our test keytool first printedCertificate was added to keystoreand then failed to save it. Run it withsudoor as administrator.Keystore was tampered with, or password was incorrect. Thecacertspassword is notchangeiton this machine; ask whoever manages the JDK.- The chain is incomplete. Java does not download missing intermediates by default. Fix the server: build the full chain file.
- The certificate does not match the address, for example
No subject alternative names matching IP address, or it has expired. Those are problems with the server certificate, not with trust.
Why this works
During the handshake, Java’s default trust manager builds a certificate chain from the server’s certificate to a trusted certificate in the trust store it loaded at startup: javax.net.ssl.trustStore if set, otherwise jssecacerts, otherwise cacerts. With your root in that store, path validation finds an anchor and succeeds. Why each runtime has its own store is in Who do we trust?, and the validation rules are in Certificate chains and path validation.
Go deeper
- keytool:
-importcert,-cacerts,-list,-deleteand thecacertsfile. - JSSE Reference Guide: the trust store system properties and their defaults.
- JDK providers: the Windows keystore types.
- Eclipse Temurin image: adding CA certificates in containers.
Early edition. Checked against the standards; an independent expert review is in progress. Found a mistake? Tell us.