Skip to content
🎉 Launch sale:50% off everything over $22 — automatically applied at checkout· ends Aug 2Shop the sale →
DevOps AI ToolKit
Newsletter
All guides
AI for HashiCorp Vault By James Joyner IV · · 9 min read Last reviewed Jul 2026

Vault Error: 'permission denied' from an Expired or Invalid Token

Quick answer

Fix Vault 403 'permission denied' caused by expired or invalid tokens: check ttl, explicit_max_ttl, periodic tokens, num_uses, revoked parents, and batch vs service tokens.

  • #vault
  • #secrets
  • #security-hardening
  • #troubleshooting
  • #errors
Free toolkit

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:8200/v1/secret/data/app/config
Code: 403. Errors:

* permission denied

Looking up the token directly makes the cause explicit:

$ vault token lookup
Error looking up token: Error making API request.

URL: GET https://vault.example.com:8200/v1/auth/token/lookup-self
Code: 403. Errors:

* bad token

What It Means

Vault returns HTTP 403 with permission denied for two structurally different situations, and the message does not distinguish them. Either the token is valid but its policies do not grant the requested capability on that path, or the token itself is no longer usable — expired, revoked, or never valid. Vault deliberately does not tell an unauthenticated caller which of the two it is, because leaking “your token is fine, you just lack access” versus “that token does not exist” is an information disclosure.

The fast way to separate them is vault token lookup-self. If lookup succeeds, the token is alive and you have an authorization problem on the specific path. If lookup itself returns 403 or bad token, the credential is dead and no policy change will help — the client must re-authenticate. Tokens die for more reasons than a lapsed TTL: num_uses can be exhausted, an explicit_max_ttl can cut a renewable token short, and revoking a parent token cascades revocation to every child it ever created.

Common Causes

  • The token’s TTL elapsed without a renewal, so Vault expired and purged it.
  • The token hit explicit_max_ttl, which caps total lifetime regardless of how often it is renewed.
  • num_uses was set at creation and the allotted number of requests has been consumed.
  • A parent token was revoked, cascading revocation to all of its non-orphan children.
  • The client holds a batch token and is trying to renew it — batch tokens are never renewable.
  • The token is valid but its attached policies do not grant read on the requested path.

Diagnostic Commands

Ask Vault about the token you are actually presenting; this is the single most useful command:

vault token lookup

The same call over the API path, showing the fields that matter for lifetime debugging:

vault read -format=json auth/token/lookup-self | \
  jq '.data | {policies, ttl, explicit_max_ttl, period, num_uses, renewable, type, orphan}'

Inspect a different token without holding it, using its accessor (safe to log, cannot be used to authenticate):

vault token lookup -accessor hmac-or-accessor-value-here

Confirm whether the failure is lifetime or policy by testing a path the token should certainly reach:

vault token capabilities secret/data/app/config

Check what the Vault server thinks of the request, including whether the node is even serving:

vault status

If the token came from a file sink, verify the file is fresh rather than a stale copy from a previous boot:

stat /run/secrets/vault-token

Step-by-Step Resolution

  1. Classify the failure. Run lookup-self; a successful lookup means the token lives and the problem is policy, not expiry:
vault token lookup || echo "token is dead - re-authenticate"
  1. If the token is alive, check its capabilities on the failing path and fix the policy rather than the token:
vault token capabilities secret/data/app/config
vault policy read app-readonly
  1. If the token is alive but nearly expired, renew it. Renewal extends by the token’s period or TTL, subject to explicit_max_ttl:
vault token renew
vault token renew -increment=1h <token>
  1. If renewals stopped working, inspect explicit_max_ttl and period. Long-lived workers should use a periodic token, which renews indefinitely as long as it is renewed within each period:
vault write auth/token/roles/worker \
  allowed_policies="app-readonly" \
  period="24h" \
  renewable=true
vault token create -role=worker
  1. If the token was created as a batch token, stop trying to renew it. Batch tokens are not persisted, carry no lease, and cannot be renewed — issue a service token for anything long-running:
vault read -field=type auth/token/lookup-self
vault token create -policy=app-readonly -type=service -ttl=1h
  1. Stop managing token lifetime in application code. Let Vault Agent auto-auth handle login and keep a token sink current, then have the app read the sink on every use:
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/secrets/vault-token" }
  }
}

Agent re-authenticates automatically when the token dies, which removes the whole class of “expired token in a long-running process” bugs. If Agent cannot authenticate at all with AppRole credentials, the error is different — see Vault error: “invalid role or secret ID”.

Prevention

  • Give long-running services periodic tokens via a token role rather than one-off tokens with a fixed max TTL.
  • Never bake num_uses into tokens for services that retry; reserve it for genuinely single-use handoffs.
  • Create tokens as orphans (-orphan) when their lifetime should not depend on the creating process’s token.
  • Log and store token accessors, not tokens, so you can audit and revoke without ever handling the secret.
  • Use Vault Agent auto-auth with a file sink so re-authentication is automatic and uniform across services.
  • Treat 403 as a retryable “re-authenticate then retry once” condition in client libraries, not a fatal error.
  • missing client token — no token was presented at all, rather than a rejected one.
  • bad token on lookup-self — the token value is unknown to Vault; it expired, was revoked, or is malformed.
  • Vault is sealed — the server cannot serve any request; every call fails regardless of token validity.
  • 1 error occurred: * permission denied on a sys/ path — valid token, but the policy omits the system path.

Frequently Asked Questions

Does restarting or sealing Vault invalidate my token? Not for service tokens, as long as the storage backend is intact — they are persisted and survive seal/unseal and restarts. Batch tokens are not persisted in the same way and in-flight ones may stop working. A token that suddenly fails after a restart usually points at lost or restored-from-old-snapshot storage.

Why did my token die before its TTL elapsed? Check three fields: explicit_max_ttl caps total lifetime independently of renewals, num_uses expires the token after a fixed request count, and revoking a parent token revokes every non-orphan child. vault token lookup shows all three.

What is the difference between ttl, explicit_max_ttl, and period? ttl is the current lifetime remaining, explicit_max_ttl is a hard ceiling on total lifetime that renewals cannot exceed, and period makes a token renewable forever in fixed increments as long as it is renewed before each period elapses. A periodic token with no explicit_max_ttl is the standard choice for daemons.

How do I revoke a token I no longer hold? Use its accessor: vault token revoke -accessor <accessor>. Accessors are safe to record in audit logs and inventories precisely because they cannot be used to authenticate. If revocation itself returns 403, the problem is your own token’s policy — see Vault error: “permission denied” on path.

Free download · 368-page PDF

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?

Free download · 368-page PDF

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.