Infrastructure worth keeping — and kept patched.

Build cache behind a corporate proxy: Gradle and Maven

Many companies make builds reach the network through a proxy. This page shows how the remote build cache behaves behind one, in Gradle and in Maven: the settings, the ways it goes wrong, and how to tell a proxy problem from a cache problem.

In our runs Maven behaved differently from Gradle: it did not skip the proxy for loopback addresses. If you test on 127.0.0.1 with Maven, list it in nonProxyHosts: see the Maven section.

Checklist for Gradle

Our advice, not a test result:

Gradle build cache behind a corporate proxy

The settings

Gradle takes proxy settings from Java system properties. Put them in ~/.gradle/gradle.properties or the project's gradle.properties, with the systemProp. prefix:

systemProp.http.proxyHost=proxy.example.com
systemProp.http.proxyPort=3128
systemProp.https.proxyHost=proxy.example.com
systemProp.https.proxyPort=3128
systemProp.http.nonProxyHosts=cache.internal.example.com|localhost|127.*

Use the https pair for an https:// cache URL and the http pair for plain HTTP. The last line lists hosts that should skip the proxy.

What we saw

For the plain-HTTP rows we set isAllowInsecureProtocol = true, which Gradle requires for non-loopback http://. That is for the test rig only; use HTTPS for real. In our runs:

SetupResultWhat the proxy logged
No proxy settings, cache at <cache-ip>second build FROM-CACHEnothing (not used)
Proxy settings, same plain-HTTP URLFROM-CACHEGET http://<cache-ip>:18130/<key>, one per build
Proxy settings, but the cache URL is 127.0.0.1FROM-CACHEnothing: Java skips the proxy for loopback addresses
Proxy settings, with the cache's address in nonProxyHostsFROM-CACHEnothing
Proxy settings, HTTPS cache, tunnel allowedFROM-CACHECONNECT <cache-ip>:18443, one per build
Proxy settings, proxy refuses the cache hostthe build succeeds but without the cache (log below)the request, answered 403

Three things that catch people

1. Testing against localhost skips the proxy. In our runs, with the cache at 127.0.0.1, Gradle never used the proxy even though it was configured. A setup that works on your laptop against a local cache proves nothing about the proxy path. Test against the real address.

2. A refusing proxy looks like a cache error. In our runs, when the proxy answered 403 to the tunnel, Gradle printed:

Could not load entry d4f2de693b561fd2c5d60b17a9232d6f from remote build cache: Loading entry from 'https://<cache-ip>:18443/d4f2de693b561fd2c5d60b17a9232d6f' response status 403: Forbidden

That is the same text you would get from the cache refusing a write with the read-only login (see the status codes). To tell them apart, take Gradle out of it and try the same route with curl:

$ curl -sS -x http://proxy.example.com:3128 https://cache.example.com/healthz
curl: (56) CONNECT tunnel failed, response 403      # the proxy said no
$ curl -sS --noproxy '*' https://cache.example.com/healthz
ok                                                   # the cache server answered

We substituted example hostnames in those commands; the two output lines are the real ones, from our test proxy refusing and from the cache answering directly. If the first one fails and the second works, ask whoever runs the proxy to allow the cache's host and port.

3. Over HTTPS the proxy only sees the address. A CONNECT tunnel shows the proxy the host and port and nothing inside, so the password and the cache keys are not visible to it. That also means any rule on the path cannot be applied. This is how the proxy log looked in our run: one CONNECT line per build.

Maven build cache extension behind a corporate proxy

Maven has its own proxy settings, and the Apache Build Cache Extension follows them.

The settings

In ~/.m2/settings.xml, next to the cache's password entry:

<settings>
  <servers>
    <server>
      <id>fosterstack-cache</id>
      <username>maven</username>
      <password>your-password-or-CI-secret</password>
    </server>
  </servers>
  <proxies>
    <proxy>
      <id>corp</id>
      <active>true</active>
      <protocol>http</protocol>
      <host>proxy.example.com</host>
      <port>3128</port>
      <nonProxyHosts>cache.internal.example.com|localhost</nonProxyHosts>
    </proxy>
  </proxies>
</settings>

The cache itself is set up as in the Maven setup. The server id must still match the remote's.

What went through the proxy

In our runs, with the proxy configured, all of the extension's cache traffic went through it: the lookups, the download that restores a build, and the uploads. This is what the proxy logged for two builds, the first filling the cache and the second restoring from it (the proxy also saw Maven's own traffic to repository.apache.org, which is not shown):

build 1 (stores)
GET http://<cache-ip>:18132/v1.1/demo/demo/<id>/buildinfo.xml
GET http://<cache-ip>:18132/v1.1/demo/demo/<id>/buildinfo.xml
PUT http://<cache-ip>:18132/v1.1/demo/demo/<id>/demo.jar
PUT http://<cache-ip>:18132/v1.1/demo/demo/<id>/buildinfo.xml
PUT http://<cache-ip>:18132/v1.1/demo/demo/<id>/build-cache-report.xml

build 2 (restores)
GET http://<cache-ip>:18132/v1.1/demo/demo/<id>/buildinfo.xml
GET http://<cache-ip>:18132/v1.1/demo/demo/<id>/buildinfo.xml
GET http://<cache-ip>:18132/v1.1/demo/demo/<id>/demo.jar
PUT http://<cache-ip>:18132/v1.1/demo/demo/<id>/build-cache-report.xml

The second build logged Found cached build, restoring demo:demo from cache.

Other cases

In our runs:

SetupWhat happened
Proxy configured, cache URL is 127.0.0.1Still went through the proxy (4 requests logged). Unlike Gradle, Maven does not skip the proxy for loopback.
Proxy configured, the cache's address in <nonProxyHosts>Nothing reached the proxy. The build still restored from the cache.
Proxy refuses the cache's hostNothing was stored or restored. The build succeeded without the cache, and logged Error downloading cache item with HTTP Status: 403.
Proxy not running at allThe lookup failed with Connect to 127.0.0.1:18131 ... Connection refused. The build succeeded without the cache, and nothing was uploaded.

Things that catch people

How we checked

Gradle run. Gradle 9.8.0 and FosterStack Cache 0.2.1. We wrote a small proxy for the test: it forwards plain HTTP, tunnels HTTPS with CONNECT, can refuse a host, and logs every request. The cache was on a non-loopback address, once over plain HTTP and once over HTTPS through nginx with a self-signed certificate that Java was told to trust.

Maven run. Maven 3.10.0, extension 1.2.3 and FosterStack Cache 0.2.1. We wrote a small proxy for the test, which forwards plain HTTP, can refuse a host, and logs every request (we made it close each connection after one request so that none go unlogged). The cache was on a non-loopback address, over plain HTTP, with a password.

Not tested in Gradle: a real corporate proxy product, a proxy that asks for a password (407), a proxy that inspects and re-signs TLS, automatic proxy configuration files, and the HTTP_PROXY variables. Not tested in Maven: HTTPS through a proxy, a proxy that asks for a password, other Maven or extension versions, and any real proxy product.

Second run: Linux, 0.2.2

The runs above were on a Mac with 0.2.1. We ran both sections again on a GitHub-hosted Linux machine with FosterStack Cache 0.2.2, Gradle 9.8.0, Maven 3.10.0 and extension 1.2.3. The cache was on the runner's own non-loopback address.

The proxy was again a small one we wrote for the test: about 150 lines that forward plain HTTP, tunnel CONNECT, can refuse a host with a 403 and log every request. It is not a production proxy product. Real proxies may ask for a password (407) or inspect TLS, and we did not test those. These results show how the cache, Gradle and Maven behave with this test proxy. They do not show how any real corporate proxy behaves. For the HTTPS rows we used a small TLS server of our own, with a self-signed certificate, in front of the cache (the Mac run used nginx), and told Java to trust it.

On the Linux run:

The run and its log are public.

Related: Gradle remote build cache not working, authentication and 401 errors and Maven build cache extension not restoring.

Gradle build cache tutorial Maven remote cache setup