Use your Gradle build cache over HTTPS with a private CA
With the right trust store, Gradle uses a cache behind HTTPS with your own certificate authority, and in our runs the cache hits worked. We ran real builds with a private CA and saw the exact error without it. With the standard Java settings we used, a trust store that held only our CA made Java ignore its normal list of trusted CAs. Our downloads from Maven Central then failed, until we put the normal list into the store as well.
The short answer
- With no trust settings, Gradle printed
PKIX path building failed, turned the remote cache off, and the build still succeeded. - Pointing Gradle at a trust store that contains your CA fixed it, from
gradle.propertiesand also from the command line. - A trust store with only your CA replaced Java's normal list of trusted authorities, and the build then failed to download from Maven Central. A store that holds the normal list plus your CA worked.
What we ran
The cache server speaks plain HTTP, so HTTPS comes from a TLS front end in front of it. For this test the front end was a small TLS proxy we wrote, a stand-in for nginx or any other TLS front end; for a real nginx setup see production Docker Compose with nginx and TLS. The certificate came from a private test CA. The address was https://127.0.0.1:18443/, and the certificate names that IP address.
All six builds ran on the same Mac, each from a fresh copy of the project and a new empty Gradle home, with Java 27 (Homebrew) and the local build cache off. The cache server (with no login) and the test certificate were shared, so runs 3 to 6 read the entry that run 2 stored. We ran the installed gradle command with --no-daemon; Gradle still started a single-use daemon for each build. Every build ended with BUILD SUCCESSFUL except run 5, which is the one that was meant to fail. Runs 1 to 4 ran gradle compileJava; runs 5 and 6 ran gradle test, because they needed Maven Central.
| Run | Trust setting | Result |
|---|---|---|
| 1 | None | PKIX path building failed. Remote cache disabled. Build succeeded, the task ran. |
| 2 | Trust store with our CA, in gradle.properties | Task ran. Run 3 then took it from the cache. |
| 3 | Same, fresh copy | compileJava FROM-CACHE |
| 4 | Trust store with our CA, on the command line only | compileJava FROM-CACHE |
| 5 | Trust store with only our CA, and a project that needs Maven Central | compileJava FROM-CACHE, so the cache over HTTPS still worked, then BUILD FAILED: could not get the JUnit file from Central, SSL handshake exception. |
| 6 | Trust store with Java's normal list plus our CA, same project | BUILD SUCCESSFUL, compileJava FROM-CACHE |
The error, as Gradle printed it
Could not load entry a7d91d3d57397e7a7b4c0f7d07e2ba1e from remote build cache: (certificate_unknown) PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
...
The remote build cache was disabled during the build due to errors.
Gradle treats a broken remote cache as non-fatal, so the build carried on without the cache. In a real pipeline that looks like a build that is simply slow, so look for this line in the log. A tool like curl fails on the same address too: in our test it exited with code 60 until we gave it the CA with --cacert.
Fix 1: a trust store with your CA
Make a Java trust store that contains your CA certificate (the password here is only the test value; keytool made a PKCS12 file, which Java reads without being told the type):
keytool -importcert -alias testca -file ca/ca.crt -keystore truststore-only.p12 -storepass changeit -noprompt
Then point Gradle at it in gradle.properties. These two lines made run 2 and run 3 work:
systemProp.javax.net.ssl.trustStore=/path/to/truststore-only.p12
systemProp.javax.net.ssl.trustStorePassword=changeit
Passing the same two settings on the command line, as -Djavax.net.ssl.trustStore=... and -Djavax.net.ssl.trustStorePassword=..., worked in run 4. We did not test how a long-running daemon picks up the setting.
The trap: your own CA replaces the normal list
With the standard Java settings we used, setting a trust store makes Java trust only what is in that store. Run 5 had only our CA, and Gradle could no longer download from Maven Central:
> Could not GET 'https://repo.maven.apache.org/maven2/org/junit/junit-bom/5.11.0/junit-bom-5.11.0.pom'.
> Got SSL handshake exception during request. It might be caused by SSL misconfiguration
> (certificate_unknown) PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
It is the same PKIX message as in run 1, this time about Maven Central.
The fix is a store that holds Java's normal list as well. Copy the cacerts file that ships with your Java (it is under lib/security in the Java folder), then add your CA to the copy:
cp /path/to/java/lib/security/cacerts truststore-combined.p12
chmod u+w truststore-combined.p12
keytool -importcert -alias testca -file ca/ca.crt -keystore truststore-combined.p12 -storepass changeit -noprompt
The Java default file uses the password changeit, and that is what worked here. The copy does not follow later updates to Java's list, so make the copy again when you update Java. Run 6 used this combined store and passed, with both the Central download and the cache over HTTPS working. Our combined store held 112 certificates: the 111 that came with Java plus ours.
How we made the test certificates
These are the exact commands in the setup script we ran before the six builds. Run them inside a folder named ca, because the keytool commands above read ca/ca.crt. The 30-day lifetime is only for the test; choose your own.
openssl genrsa -out ca.key 2048
openssl req -x509 -new -key ca.key -sha256 -days 30 -subj "/CN=Test Private CA" -out ca.crt
openssl genrsa -out server.key 2048
openssl req -new -key server.key -subj "/CN=cache.test" -out server.csr
printf "subjectAltName=IP:127.0.0.1,DNS:localhost\nbasicConstraints=CA:FALSE\nextendedKeyUsage=serverAuth\n" > ext.cnf
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 30 -sha256 -extfile ext.cnf
The certificate has to name the address your builds use. We did not test a certificate that names a different host, an expired certificate, the Gradle wrapper (./gradlew), or Linux or Windows.
If it still does not work
Work through Gradle remote build cache not working. For the server side of HTTPS, see production Docker Compose with nginx and TLS.
Run on Gradle 9.8.0 with FosterStack Cache 0.2.1 behind a test TLS proxy, macOS (arm64), Java 27 (Homebrew), OpenSSL 3.6.5.
We repeated the six runs on a GitHub-hosted Linux machine with release 0.2.2, Gradle 9.8.0, Java 27 (Temurin 27+35) and a small TLS proxy written in Python. All six runs matched the table, including run 5, which fails as expected. The combined store there held 121 certificates, including ours: the count depends on which Java build you use. The run and its log are public.