Vault Error: 'missing client token' on Every API Call
Fix Vault's 400 'missing client token': set VAULT_TOKEN, send X-Vault-Token, use vault login and ~/.vault-token, and wire CI runners and Vault Agent sinks correctly.
- #vault
- #secrets
- #security-hardening
- #troubleshooting
- #errors
Stuck on this HashiCorp Vault error? Get the free incident triage checklist
A one-page PDF — the exact steps to isolate, fix, and verify a production error like this one. No spam, unsubscribe anytime.
Exact Error Message
$ vault kv get secret/app/config
Error making API request.
URL: GET https://vault.example.com/v1/secret/data/app/config
Code: 400. Errors:
* missing client token
The same condition from a raw HTTP client that forgot the header:
$ curl -s -i https://vault.example.com/v1/auth/token/lookup-self
HTTP/2 400
content-type: application/json
{"errors":["missing client token"]}
What It Means
Vault could not find a token on the request at all. Every path outside the small unauthenticated set — sys/health, sys/seal-status, sys/init, and the login endpoints under auth/*/login — requires a client token, supplied either in the X-Vault-Token header or as Authorization: Bearer <token>. When neither header is present, Vault rejects the request before any policy evaluation happens and returns HTTP 400.
The status code is the key distinction. A 400 with missing client token means no credential was sent. A 403 with permission denied means a token was sent and Vault either could not validate it or the attached policies did not allow the operation. If you are debugging a client and see 400, stop looking at policies entirely — the problem is upstream, in how the token is being sourced or passed. For the CLI this almost always means VAULT_TOKEN is unset and there is no ~/.vault-token helper file to fall back on.
Common Causes
VAULT_TOKENis not exported in the shell or process environment.- A custom HTTP client sends no
X-Vault-Tokenheader, or spells it wrong. ~/.vault-tokendoes not exist becausevault loginwas never run, or runs as a different user.- A CI/CD runner starts with a clean environment and no login step before the Vault call.
- A Vault Agent sink file is empty or not yet written when the application reads it.
- The token is passed as a shell variable that was never exported, so child processes do not inherit it.
Diagnostic Commands
Confirm whether the CLI has a token at all, and where it came from:
vault print token
env | grep -E '^VAULT_(TOKEN|ADDR|NAMESPACE)='
Check for the token helper file the CLI writes on login:
ls -l ~/.vault-token && wc -c ~/.vault-token
Verify the token is valid by looking it up against the server:
vault token lookup -format=json | jq '.data | {display_name, policies, ttl}'
Reproduce with an explicit header so you can prove the transport is fine:
curl -s -H "X-Vault-Token: $VAULT_TOKEN" \
https://vault.example.com/v1/auth/token/lookup-self | jq
For agent-based setups, confirm the sink file exists and is non-empty:
ls -l /run/vault/agent-token && head -c 8 /run/vault/agent-token
Step-by-Step Resolution
- Authenticate.
vault loginwrites the resulting token to~/.vault-token, which the CLI reads automatically on every later command:
vault login -method=userpass username=alice
# or with a plain token
vault login s.XXXXXXXXXXXXXXXX
vault token lookup
- For non-interactive use, capture only the token field and export it.
-field=tokenavoids writing the helper file when combined with-no-store:
export VAULT_ADDR=https://vault.example.com
export VAULT_TOKEN="$(vault write -field=token -no-store auth/approle/login \
role_id="$ROLE_ID" secret_id="$SECRET_ID")"
vault token lookup
- Fix HTTP clients by sending the header explicitly. Both forms are accepted; use one, not a malformed mix:
curl -H "X-Vault-Token: $VAULT_TOKEN" \
https://vault.example.com/v1/secret/data/app/config
curl -H "Authorization: Bearer $VAULT_TOKEN" \
https://vault.example.com/v1/secret/data/app/config
- In CI, log in inside the job rather than assuming an inherited environment. A GitHub Actions or GitLab runner starts clean, and
exportin an earlier step does not survive into the next one unless you persist it:
# inside a single CI step
export VAULT_ADDR="$VAULT_ADDR"
VAULT_TOKEN="$(vault write -field=token -no-store auth/jwt/login \
role=ci-deploy jwt="$CI_JOB_JWT")"
export VAULT_TOKEN
vault kv get -field=password secret/data/ci/db
If the AppRole credentials themselves are rejected at this step, that is a different failure — see Vault error: “invalid role_id / secret_id”. For Kubernetes workloads authenticating with a projected service account token, see Vault error: Kubernetes auth service account token.
- For Vault Agent, make the application wait for the sink file and read it at request time rather than at process start. Agent writes the sink after its own login succeeds, which may be seconds after your container starts:
auto_auth {
method "approle" {
config = {
role_id_file_path = "/etc/vault/role_id"
secret_id_file_path = "/etc/vault/secret_id"
}
}
sink "file" {
config = {
path = "/run/vault/agent-token"
mode = 0640
}
}
}
until [ -s /run/vault/agent-token ]; do sleep 1; done
export VAULT_TOKEN="$(cat /run/vault/agent-token)"
- If you are on Vault Enterprise with namespaces, set the namespace separately. A namespace is not part of the token header, and putting it in the wrong place produces confusing failures — an unset namespace sends a valid token to the root namespace where the mount does not exist:
export VAULT_NAMESPACE=team-platform
# equivalent header form
curl -H "X-Vault-Token: $VAULT_TOKEN" \
-H "X-Vault-Namespace: team-platform" \
https://vault.example.com/v1/secret/data/app/config
Once a token is flowing and you start seeing 403s instead of 400s, you have moved from an authentication problem to an authorization one — continue with Vault error: “permission denied” on a path.
Prevention
- Standardise on a login step at the start of every job rather than long-lived exported tokens.
- Use Vault Agent or a sidecar for long-running workloads so token renewal is handled for you.
- Fail fast in application startup with an explicit check that
VAULT_TOKEN(or the sink file) is non-empty. - Never bake tokens into images or commit
~/.vault-token; add it to your global gitignore. - Set
VAULT_ADDRandVAULT_NAMESPACEin the same place you set the token so the three cannot drift apart. - Add a health probe that calls
auth/token/lookup-selfso a missing token surfaces before real traffic does.
Related Errors
permission denied(403) — a token was sent, but it is invalid, expired, or lacks the capability.missing client tokenonsys/paths — the endpoint is root-protected; unauthenticated access is not available there.namespace not found—X-Vault-Namespacenames a namespace that does not exist on the cluster.no handler for route— the mount path is wrong, often the result of an unset namespace on Enterprise.
Frequently Asked Questions
Why does the CLI work on my laptop but fail in CI? Locally the CLI silently reads ~/.vault-token written by an earlier vault login. CI runners have no such file and no exported VAULT_TOKEN, so the request goes out with no header.
Is X-Vault-Token or Authorization: Bearer preferred? Both work. X-Vault-Token is the native header; the Bearer form exists so standard HTTP tooling and proxies can carry the credential. Send exactly one.
Why do I get 400 instead of 403 for a revoked token? You should get 403 for a revoked token. A 400 means the header was absent entirely — check for an empty variable expanding to nothing, which is the usual culprit.
Should I use -no-store when logging in? Yes for automation, so the token is never written to disk and lives only in the process environment. Keep the default file behaviour for interactive human use. More auth-method walkthroughs are in the Vault guides.
Fixed it? Get 500 HashiCorp Vault & DevOps AI prompts — free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.
Did this fix your issue?
Get 500 Battle-Tested DevOps AI Prompts — Free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.