Skip to content
Latchkey

JFrog "401 Unauthorized" (JF_ACCESS_TOKEN) in CI

Artifactory returned HTTP 401 because the jf CLI sent no valid credential. The request reached the server, but JF_ACCESS_TOKEN (or the legacy API key) was empty, unset in the step, or expired.

What this error means

A jf rt upload, jf rt download, or jf config add step fails with "401 Unauthorized" or "Bad credentials". Public reads may work while authenticated operations fail.

jf
[Error] server response: 401 Unauthorized
{
  "errors": [ { "status": 401, "message": "Bad credentials" } ]
}

Diagnose it: registry, auth, and version resolution

Terminal
# which registry is actually in effect, and with what credentials?
<pkg-tool> config list 2>/dev/null | grep -iE "registry|auth|token"

# does the exact version resolve from the runner?
<pkg-tool> view <package> versions 2>&1 | tail -3

Common causes

JF_ACCESS_TOKEN was never exposed to the step

The token lives in a repository secret but the env var was not set on the job or step, so the jf CLI authenticates anonymously and Artifactory returns 401.

The access token has expired

JFrog access tokens carry an expiry. Once past it, every authenticated call returns 401 until a fresh token is issued and stored as the secret.

How to fix it

Provide JF_URL and JF_ACCESS_TOKEN via secrets

  1. Store the Artifactory base URL and an access token as repository or organization secrets.
  2. Expose them as JF_URL and JF_ACCESS_TOKEN in the step env so setup-jfrog-cli configures the CLI automatically.
  3. Re-run and confirm jf rt ping returns OK.
.github/workflows/ci.yml
- uses: jfrog/setup-jfrog-cli@v4
  env:
    JF_URL: ${{ vars.JF_URL }}
    JF_ACCESS_TOKEN: ${{ secrets.JF_ACCESS_TOKEN }}
- run: jf rt ping

Rotate an expired token

A 401 that started overnight is usually an expired token. Generate a new access token in the JFrog Platform and update the secret in one place.

Terminal
jf rt ping --url "$JF_URL" --access-token "$JF_ACCESS_TOKEN"

How to prevent it

  • Keep JF_ACCESS_TOKEN in CI secrets, never committed to the repo.
  • Prefer long-lived reference tokens or OIDC over short expiries for CI.
  • Verify auth early with jf rt ping before upload or download steps.

Frequently asked questions

What causes JFrog "401 Unauthorized" (JF_ACCESS_TOKEN) in CI?
There are 2 common causes: jf_access_token was never exposed to the step and the access token has expired. The token lives in a repository secret but the env var was not set on the job or step, so the jf CLI authenticates anonymously and Artifactory returns 401.
How do I fix JFrog "401 Unauthorized" (JF_ACCESS_TOKEN) in CI?
There are 2 fixes depending on which cause you have: provide jf_url and jf_access_token via secrets and rotate an expired token. Work through them in order, since the first is the most common.
What does JFrog "401 Unauthorized" (JF_ACCESS_TOKEN) in CI actually mean?
A jf rt upload, jf rt download, or jf config add step fails with "401 Unauthorized" or "Bad credentials".
How do I stop JFrog "401 Unauthorized" (JF_ACCESS_TOKEN) in CI happening again?
Keep JF_ACCESS_TOKEN in CI secrets, never committed to the repo. The prevention section lists 3 changes that keep it from recurring.

Related guides

References

Not every red build is your code. Latchkey repairs the ones that are not, on the runner. Start free → 30-day trial · No credit card