hashicorp/nomad · error
cannot update expiration time
Error message
cannot update expiration time
What it means
Nomad ACL tokens support an expiration TTL and, once created, a computed expiration time. During token update, the Validate/merge logic compares the incoming token against the existing one and rejects any mutation of expiration fields, because expiry is immutable after creation.
Source
Thrown at nomad/structs/acl.go:819
fmt.Errorf("expiration time cannot be more than %s in the future (was %s)",
maxTTL, expiresIn))
} else if expiresIn < minTTL {
mErr.Errors = append(mErr.Errors,
fmt.Errorf("expiration time cannot be less than %s in the future (was %s)",
minTTL, expiresIn))
}
}
default:
if existing.Global != a.Global {
mErr.Errors = append(mErr.Errors, errors.New("cannot toggle global mode"))
}
if existing.ExpirationTTL != a.ExpirationTTL {
mErr.Errors = append(mErr.Errors, errors.New("cannot update expiration TTL"))
}
if a.ExpirationTime != nil {
if !existing.ExpirationTime.Equal(*a.ExpirationTime) {
mErr.Errors = append(mErr.Errors, errors.New("cannot update expiration time"))
}
}
}
return mErr.ErrorOrNil()
}
// HasExpirationTime checks whether the ACL token has an expiration time value
// set.
func (a *ACLToken) HasExpirationTime() bool {
if a == nil || a.ExpirationTime == nil {
return false
}
return !a.ExpirationTime.IsZero()
}
// IsExpired compares the ACLToken.ExpirationTime against the passed t toView on GitHub (pinned to 482b49bf1a)
Solutions
- Remove the ExpirationTime (and ExpirationTTL) fields from the update request so only mutable fields are changed
- Create a new token with the desired expiration instead of updating the existing one
- If you must keep sending the full struct, send ExpirationTime exactly as returned by the server (same instant, equal time comparison)
Example fix
// before
token.ExpirationTime = time.Now().Add(24 * time.Hour)
client.ACLTokens().Update(token, nil)
// after
newToken := &structs.ACLToken{Name: token.Name, Policies: token.Policies, ExpirationTTL: 24 * time.Hour}
client.ACLTokens().Create(newToken, nil) Defensive patterns
Strategy: validation
Validate before calling
func canUpdate(existing, incoming *structs.ACLToken) error {
if incoming.ExpirationTime != nil && existing.ExpirationTime != nil &&
!existing.ExpirationTime.Equal(*incoming.ExpirationTime) {
return errors.New("expiration is immutable: create a new token instead")
}
return nil
} Prevention
- Never mutate ExpirationTime/ExpirationTTL on fetched tokens before update calls
- Build update requests from a minimal struct containing only mutable fields
- Treat token expiry changes as create-new-token operations
When it happens
Trigger: Calling the ACL token update API (ACL.UpsertTokens / token Update RPC) with a token whose ExpirationTime differs from the stored token's ExpirationTime, e.g. copying an old token struct and altering ExpirationTime, or round-tripping a token where the server recomputed the time.
Common situations: Operators trying to extend or shorten a token's life by editing its expiration via the update endpoint; client code that copies a GET'd token, mutates expiration fields, and PUTs it back; tooling that normalizes timestamps and introduces a slight time drift so the times no longer compare equal.
Related errors
- token name too long
- client token missing policies or roles
- management token cannot be associated with policies or roles
- token type must be client or management
- expiration time cannot be before create time
AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04).
Data as JSON: /api/errors/a7d709f0f5cff083.
Report an issue: GitHub.