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" } ]
}

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.

Related guides

Tired of flaky CI? Latchkey auto-heals failed jobs and retries them for you. Start free →