Infrastructure worth keeping — and kept patched.

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

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.

RunTrust settingResult
1NonePKIX path building failed. Remote cache disabled. Build succeeded, the task ran.
2Trust store with our CA, in gradle.propertiesTask ran. Run 3 then took it from the cache.
3Same, fresh copycompileJava FROM-CACHE
4Trust store with our CA, on the command line onlycompileJava FROM-CACHE
5Trust store with only our CA, and a project that needs Maven CentralcompileJava FROM-CACHE, so the cache over HTTPS still worked, then BUILD FAILED: could not get the JUnit file from Central, SSL handshake exception.
6Trust store with Java's normal list plus our CA, same projectBUILD 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.