Gradle build cache authentication: 401 and status codes
Gradle does not fail the build when the cache refuses it, so a wrong password or a refused upload often shows up only as a status code. FosterStack Cache takes an optional password, and this page shows how to set it, how to give Gradle the right one, and what each status code means.
Our advice, not a test result
- Use HTTPS. Gradle sends the password with every request, so over plain HTTP it travels in the clear. Put real servers behind HTTPS.
- Keep the
credentialsblock even where the server has no password. One config then works against both a test server and a real one. - Give laptops the read-only login. That is the intended way to give laptops a cache they can read and not change. Use the read-write login for machines that upload, or set the client not to upload.
Turn on a password on the server
FSCACHE_USERNAME=gradle FSCACHE_PASSWORD=<a long secret> ./fscache
This is HTTP Basic Auth. The health and metrics paths stay open without a password, so monitoring still works. The status page does not. For a second, read-only login see let CI write, developers only read.
Tell Gradle the password
In settings.gradle.kts:
// FosterStack Cache — remote Gradle build cache.
// https://github.com/fosterstack/cache
buildCache {
remote<HttpBuildCache> {
url = uri("https://cache.example.com/")
isPush = true
credentials {
username = "gradle"
password = System.getenv("FSCACHE_PASSWORD")
}
}
}
Three ways to supply the password
In our runs all three gave :compileJava FROM-CACHE:
- An environment variable, as above:
export FSCACHE_PASSWORD=.... Good for CI, where the secret store sets it. - Your user
~/.gradle/gradle.properties, withpassword = providers.gradleProperty("fscachePassword").orNullin the settings file andfscachePassword=...in that file. Never put it in the project's owngradle.properties, which gets committed. - The command line:
gradle build -PfscachePassword=.... Handy for one-off tests; it also shows up in the process list.
What each status code means
Gradle talks to the cache with plain web requests, and it does not fail the build when the cache says no. So the status code is often the only clue.
| Status | When you get it | What to do |
|---|---|---|
| 200 | A stored entry was found and sent. | Nothing. This is a cache hit. |
| 201 | An upload was stored. | Nothing. |
| 404 | That key is not stored. | Usually nothing. It is a cache miss. With a correct password it means the key is not stored. |
| 401 | No password, or the wrong one. | Fix the credentials block. See below. |
| 403 | A write sent with the read-only login. | Use the read-write login for machines that upload, or set the client not to upload. |
| 413 | An upload bigger than the server's size limit for one entry, or bigger than the whole cache size cap. | With no X-FSCache-Reject header, the entry is over the per-entry limit: raise FSCACHE_MAX_BODY_BYTES if it is meant to be cached. With the header set to entry-exceeds-cache-cap, the entry is bigger than FSCACHE_MAX_BYTES: see how the size limit works. |
| 429 | Too many uploads in progress at once. The server allows 32 by default, and a refused upload stores nothing. | The build carries on without that entry. If it keeps happening, see how many builds can use one cache server at once for the setting. |
401: a wrong or missing password
Gradle treats a broken remote cache as non-fatal, so the build still succeeds, just without the cache. In our runs it printed this at the default log level, with a wrong password and with none:
Could not load entry d4f2de693b561fd2c5d60b17a9232d6f from remote build cache: Loading entry from 'http://127.0.0.1:18099/d4f2de693b561fd2c5d60b17a9232d6f' response status 401: Unauthorized
If you see that line, the password Gradle sent was wrong or missing. The usual causes are no credentials block, a wrong password, or a typo. Run with -i for more detail. To test the server directly:
curl -s -o /dev/null -w '%{http_code}\n' -u gradle:wrong-password https://cache.example.com/some-key
A 401 here means the credentials are wrong. A 404 means they are right and the key simply is not stored. More steps are in Gradle remote build cache not working.
403: the login worked but may not write
The server can have an optional read-only login. A client holding it can read (GET and HEAD return 200 as normal) but any write gets a 403. See can someone poison our shared build cache.
413: entry size
A single upload over the limit is refused with 413 and nothing is stored, so a later request for that key is a 404. The limit is FSCACHE_MAX_BODY_BYTES and defaults to 1 GiB per entry.
If the password seems ignored
./gradlew --stop is a quick way to rule the Gradle daemon out. In our test a changed password was picked up without a restart.
How we checked
A 0.2.1 server and Gradle 9.8.0 on macOS, started with a read-write login, a read-only login, and the body limit set to 1024 bytes. Your numbers for the limit will differ, since the default is 1 GiB. This list comes from these runs:
GET /healthz (no credentials) 200
GET /metrics (no credentials) 200
GET key, no credentials 401
GET key, wrong password 401
GET missing key, read-write login 404
PUT 5 bytes, read-write login 201
GET stored key, read-write login 200
GET stored key, read-only login 200
HEAD stored key, read-only login 200
PUT, no credentials 401
PUT, read-only login 403
PUT 2048 bytes (limit set to 1024), read-write login 413
GET the too-large key afterwards 404
/statusz, no credentials 401
- Credentials block, no password on the server. We tested a client with the block against a server with authentication switched off: the build stored an entry and the next build got a hit.
- The Gradle daemon. We started a daemon with no password, exported the right one in the same shell, and the next build used it (same daemon process, no restart) and got
FROM-CACHE. Then we exported a wrong one and the next build printed the 401 line straight away. We tested this only on macOS with Gradle 9.8.0; the Linux run below did not repeat it. - Plain HTTP. Gradle refuses plain
http://unless you opt in withisAllowInsecureProtocol. In our test the address127.0.0.1was let through without it, but the namelocalhostwas refused.
Second run, on a GitHub-hosted Linux machine with FosterStack Cache 0.2.2 and Gradle 9.8.0. The results:
- All fourteen status codes in the list above were the same.
- Each of the three ways to supply the password gave
:compileJava FROM-CACHE. A wrong password, and none at all, printed the 401 line at the default log level and the build succeeded. - An entry bigger than the total cache size limit got a 413 with the header
X-FSCache-Reject: entry-exceeds-cache-cap. The server sent the name asX-Fscache-Reject, which is the same header, because header names are not case-sensitive. - An entry over the per-entry limit got a 413 with no such header.
- With the default limit, we started 40 uploads at once and left them unfinished. 8 were refused with a 429 straight away and 32 were still open. The server held no entries afterwards, because no upload was completed.
- A
DELETEwith the read-only login also got a 403.
The run and its log are public. Not tested there: the 1 GiB default for FSCACHE_MAX_BODY_BYTES, which needs an entry bigger than that.
Related: Gradle remote build cache not working, server sizing and monitoring.