hashicorp/nomad · error
cannot update expiration TTL
Error message
cannot update expiration TTL
What it means
A validation error from ACLToken.Validate on update: a token's ExpirationTTL is immutable after creation. Nomad rejects updates where the submitted TTL differs from the existing token's ExpirationTTL.
Source
Thrown at nomad/structs/acl.go:815
// check this against the regions max and min values.
expiresIn := a.ExpirationTime.Sub(a.CreateTime)
if expiresIn > maxTTL {
mErr.Errors = append(mErr.Errors,
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
}View on GitHub (pinned to 482b49bf1a)
Solutions
- Leave ExpirationTTL unchanged when updating the token
- To change lifetime, create a new token with the desired TTL and migrate workloads
- Copy ExpirationTTL verbatim from the existing token into the update request
Example fix
// before
update := existing
update.ExpirationTTL = 72 * time.Hour // immutable
// after
update := existing // ExpirationTTL unchanged
newToken := &api.ACLToken{Name: existing.Name, Type: existing.Type, Policies: existing.Policies, ExpirationTTL: 72 * time.Hour}
apiClient.ACL().Create(newToken, nil) Defensive patterns
Strategy: validation
Validate before calling
if existing != nil && existing.ExpirationTTL != update.ExpirationTTL {
return errors.New("ExpirationTTL is immutable; create a new token to change lifetime")
} Type guard
func expirationTTLStable(existing, update *structs.ACLToken) bool {
return existing == nil || existing.ExpirationTTL == update.ExpirationTTL
} Try / catch
if err := update.Validate(minTTL, maxTTL, existing); err != nil {
if strings.Contains(err.Error(), "cannot update expiration TTL") {
update.ExpirationTTL = existing.ExpirationTTL
err = update.Validate(minTTL, maxTTL, existing)
}
} Prevention
- Never modify ExpirationTTL on updates; copy it from the existing token
- Implement token rotation (create new, migrate, delete old) instead of editing TTLs
- Validate updates against the existing token before submission
When it happens
Trigger: Calling ACL Upsert (update) on an existing token with a modified ExpirationTTL value (including zeroing it or setting it on a token created without one).
Common situations: Automation extending/renewing token lifetime by editing the TTL; token renewal scripts that mistakenly update instead of recreating; config drift tools rewriting all fields to defaults.
Related errors
- cannot toggle global mode
- 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
AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04).
Data as JSON: /api/errors/99917eae1541fe33.
Report an issue: GitHub.