hashicorp/nomad · error
invalid signature: %w
Error message
invalid signature: %w
What it means
The JWT parsed but its signature failed verification against the typed public key looked up by KeyID (token.Claims(typedPubKey, &claims)). The token was not signed by the keyring key corresponding to its kid header, or the key is no longer available.
Source
Thrown at nomad/encrypter.go:386
return nil, err
}
// Find the key material
pubKey, err := e.waitForPublicKey(keyID)
if err != nil {
return nil, err
}
typedPubKey, err := pubKey.GetPublicKey()
if err != nil {
return nil, err
}
claims := structs.IdentityClaims{}
// Validate the claims.
if err := token.Claims(typedPubKey, &claims); err != nil {
return nil, fmt.Errorf("invalid signature: %w", err)
}
// COMPAT: Until we can guarantee there are no pre-1.7 JWTs in use, we can
// only validate the signature and have no further expectations of the
// claims.
if err := claims.Validate(jwt.Expected{}); err != nil {
return nil, fmt.Errorf("invalid claims: %w", err)
}
return &claims, nil
}
// AddUnwrappedKey stores the key in the keystore and creates a new cipher for
// it. This is called in the RPC handlers on the leader and from the legacy
// KeyringReplicator.
func (e *Encrypter) AddUnwrappedKey(rootKey *structs.UnwrappedRootKey, isUpgraded bool) (*structs.RootKey, error) {
// note: we don't lock the keyring here but inside addCipherView on GitHub (pinned to 482b49bf1a)
Solutions
- Issue a fresh token (re-run the workload or regenerate the claim) — the old key may have been rotated out
- Ensure all servers share the same keystore/keys (keyring replication caught up)
- Verify the token's kid matches a key that still exists: nomad keyring list
- Check for cross-cluster token reuse and use the issuing cluster for verification
Example fix
// before: verifying a stale pre-rotation token claims, err := encrypter.VerifyClaim(oldToken) // invalid signature // after: fetch a fresh signed claim for the allocation token, _, err := client.Allocations().GetAllocByID(allocID) // re-read identity token claims, err := encrypter.VerifyClaim(token)
Defensive patterns
Strategy: try-catch
Validate before calling
// check the kid exists in the current keyring before verification
keyring, _, err := client.Keyring().List(nil)
if err != nil { return err }
haveKey := false
for _, k := range keyring {
if strings.HasPrefix(k.KeyID, sigAlgPrefixFromToken(token)) { haveKey = true }
}
if !haveKey { return fmt.Errorf("token signed with unknown/rotated-out key") } Try / catch
claims, err := encrypter.VerifyClaim(token)
if err != nil {
if strings.Contains(err.Error(), "invalid signature") {
// stale or foreign-cluster token: request a fresh token from the issuer
return fmt.Errorf("token not signed by a current keyring key — re-issue: %w", err)
}
return err
} Prevention
- Re-issue tokens after keyring rotations instead of caching them long-term
- Never reuse workload identity tokens across clusters/regions
- Keep keys in the keyring through at least the max token lifetime before removing them
- Ensure all servers have replicated keystore data before retiring old keys
When it happens
Trigger: VerifyClaim resolves the kid to a public key in the keyring and calls token.Claims(pubKey, &claims); mismatched signing key, token signed by a removed/rotated key, or token from a different cluster produces this error.
Common situations: Tokens issued before a keyring rotation are presented after the old key was removed, multi-cluster setups where a token signed by cluster A's key is verified by cluster B, or the verifying server hasn't replicated the newer key yet.
Related errors
- failed to parse signed token: %w
- invalid claims: %w
- node does not have a JWT identity token
- ErrNoKeyID
- root key ID %s must match key file %s
AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04).
Data as JSON: /api/errors/b6d2cf8c04f5ae9a.
Report an issue: GitHub.