hashicorp/nomad · error

invalid MaxTokenTTL value '%s' (should be between %s and %s)

Error message

invalid MaxTokenTTL value '%s' (should be between %s and %s)

What it means

ACLAuthMethod.Validate() rejects MaxTokenTTL when it is not within [minTTL, maxTTL], the bounds supplied by the server (system defaults or agent config). Both the error message and bounds are formatted with time.Duration.String().

Source

Thrown at nomad/structs/acl.go:1448

		mErr.Errors = append(mErr.Errors, fmt.Errorf("invalid name '%s'", a.Name))
	}

	if !slices.Contains([]string{ACLAuthMethodTokenLocalityLocal, ACLAuthMethodTokenLocalityGlobal}, a.TokenLocality) {
		mErr.Errors = append(
			mErr.Errors, fmt.Errorf("invalid token locality '%s'", a.TokenLocality))
	}

	if !slices.Contains(ValidACLAuthMethodTypes, a.Type) {
		mErr.Errors = append(
			mErr.Errors, fmt.Errorf("invalid token type '%s'", a.Type))
	}

	if err := a.Config.Validate(a.Type); err != nil {
		mErr.Errors = append(mErr.Errors, fmt.Errorf("invalid config: %w", err))
	}

	if minTTL > a.MaxTokenTTL || a.MaxTokenTTL > maxTTL {
		mErr.Errors = append(mErr.Errors, fmt.Errorf(
			"invalid MaxTokenTTL value '%s' (should be between %s and %s)",
			a.MaxTokenTTL.String(), minTTL.String(), maxTTL.String()))
	}

	return mErr.ErrorOrNil()
}

// Sanitize returns a copy of the ACLAuthMethod with any secrets redacted
func (a *ACLAuthMethod) Sanitize() *ACLAuthMethod {
	if a == nil || a.Config == nil {
		return a
	}
	// copy to ensure we do not mutate a pointer pulled directly out of state.
	clean := a.Copy()
	// clean nested structs here, so it's obvious what all is being cleaned
	// in one spot, rather than follow a stack of sanitization calls.
	if clean.Config.OIDCClientSecret != "" {
		clean.Config.OIDCClientSecret = "redacted"

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Set MaxTokenTTL between the server's min and max (check agent config: audit the ACL token TTL defaults, e.g. 1m–24h).
  2. Read the error message bounds and align the value to them.
  3. Set MaxTokenTTL explicitly instead of relying on zero-value; use time.Duration constants in Go.
  4. If a longer TTL is genuinely needed, raise the server-side max token TTL config, then re-submit.

Example fix

// before
am.MaxTokenTTL = 0 // "no expiry"
// after
am.MaxTokenTTL = 24 * time.Hour
Defensive patterns

Strategy: validation

Validate before calling

minTTL, maxTTL := 1*time.Minute, 24*time.Hour // confirm with server config
if am.MaxTokenTTL < minTTL || am.MaxTokenTTL > maxTTL {
	return fmt.Errorf("MaxTokenTTL %s outside [%s, %s]", am.MaxTokenTTL, minTTL, maxTTL)
}

Try / catch

if err := am.Validate(minTTL, maxTTL); err != nil {
	if strings.Contains(err.Error(), "invalid MaxTokenTTL") {
		// parse bounds from message and clamp before retry
	}
	return err
}

Prevention

When it happens

Trigger: Upserting an ACL auth method whose MaxTokenTTL is below the server minimum or above the server maximum (e.g. 1s when min is 1m, or 0/8760h when the configured max is 24h). Also triggered when minTTL > MaxTokenTTL with a zero MaxTokenTTL where a nonzero is required.

Common situations: Setting MaxTokenTTL: "0s" expecting 'no expiry'; guessing TTLs in a config where the operator changed the server's default/max ACL token TTLs; unit mistakes like "2h" vs "2m".

Related errors


AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04). Data as JSON: /api/errors/c39a1f81695809ee. Report an issue: GitHub.