hashicorp/nomad · error

expiration time cannot be before create time

Error message

expiration time cannot be before create time

What it means

A validation error from ACLToken.Validate: when a token specifies an ExpirationTime, that time must be after the token's CreateTime. Nomad rejects tokens whose expiration predates creation because they would be born already expired.

Solutions

  1. Set ExpirationTime strictly after CreateTime (use server time or add a TTL buffer)
  2. Prefer setting ExpirationTTL and let the server compute the absolute time
  3. Synchronize clocks (NTP) on hosts generating expiration timestamps

Example fix

// before
token.ExpirationTime = &pastTime
token.CreateTime = now
// after
exp := time.Now().Add(24 * time.Hour)
token.ExpirationTime = &exp
Defensive patterns

Strategy: validation

Validate before calling

if token.ExpirationTime != nil && !token.ExpirationTime.IsZero() && token.CreateTime.After(*token.ExpirationTime) {
    return errors.New("expiration time must be after create time")
}

Type guard

func expirationAfterCreate(t *structs.ACLToken) bool {
    return t.ExpirationTime == nil || t.ExpirationTime.IsZero() || t.CreateTime.Before(*t.ExpirationTime)
}

Try / catch

if err := token.Validate(minTTL, maxTTL, nil); err != nil {
    if strings.Contains(err.Error(), "expiration time cannot be before create time") {
        exp := time.Now().Add(24 * time.Hour)
        token.ExpirationTime = &exp
        err = token.Validate(minTTL, maxTTL, nil)
    }
}

Prevention

When it happens

Trigger: Creating/updating an ACLToken where CreateTime.After(*ExpirationTime) is true — e.g. an expiration computed from a clock-skewed host, or copying a token's expiration while resetting CreateTime to now.

Common situations: Clock skew between the client computing the expiration and the server validating create time; template-based token generation using a stale/past timestamp; tests with hardcoded times.

Related errors


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

Appendix: source

Thrown at nomad/structs/acl.go:793

			mErr.Errors = append(mErr.Errors, errors.New("management token cannot be associated with policies or roles"))
		}
	default:
		mErr.Errors = append(mErr.Errors, errors.New("token type must be client or management"))
	}

	// There are different validation rules depending on whether the ACL token
	// is being created or updated.
	switch existing {
	case nil:
		if a.ExpirationTTL < 0 {
			mErr.Errors = append(mErr.Errors,
				fmt.Errorf("token expiration TTL '%s' should not be negative", a.ExpirationTTL))
		}

		if a.ExpirationTime != nil && !a.ExpirationTime.IsZero() {

			if a.CreateTime.After(*a.ExpirationTime) {
				mErr.Errors = append(mErr.Errors, errors.New("expiration time cannot be before create time"))
			}

			// Create a time duration which details the time-til-expiry, so we can
			// 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 {

View on GitHub (pinned to 482b49bf1a)