hashicorp/nomad · error

expiration time cannot be less than %s in the future (was %s

Error message

expiration time cannot be less than %s in the future (was %s)

What it means

Complementing the max check, Validate rejects ExpirationTime values less than minTTL after CreateTime with 'expiration time cannot be less than %s in the future (was %s)'. This prevents creating tokens that are effectively already expired or expire within an unusably short window.

Source

Thrown at nomad/structs/acl.go:806

		}

		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 {
			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"))
			}
		}

	}

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Set ExpirationTime at least minTTL after CreateTime (check the server's min TTL config).
  2. Use token deletion (`nomad acl token delete`) for immediate revocation instead of near-term expiry.
  3. Synchronize clocks (NTP) between the client constructing the token and the Nomad servers.
  4. Compute expiry with CreateTime from the server response rather than local time to avoid skew.

Example fix

// before
exp := token.CreateTime // expires immediately
// after
minTTL := 5 * time.Minute
exp := token.CreateTime.Add(minTTL)
token.ExpirationTime = &exp
Defensive patterns

Strategy: validation

Validate before calling

func validateMinExpiry(createTime, expiry time.Time, minTTL time.Duration) error {
    if expiry.Sub(createTime) < minTTL {
        return fmt.Errorf("expiry %s is below min TTL %s", expiry, minTTL)
    }
    return nil
}

Try / catch

if err := token.Validate(nil); err != nil {
    if strings.Contains(err.Error(), "cannot be less than") {
        exp := token.CreateTime.Add(minTokenTTL)
        token.ExpirationTime = &exp
        return token.Validate(nil)
    }
    return err
}

Prevention

When it happens

Trigger: Creating an ACL token whose ExpirationTime is sooner than minTTL after CreateTime — e.g. expiry equal to create time, an expiry in the past, or a TTL below the server-configured minimum.

Common situations: Tests or scripts computing expiry from a stale clock (client/server clock skew); accidentally passing create time as expiry; timezone/UTC conversion errors shifting the timestamp backwards; very short TTLs intended as immediate revocation instead of using token delete.

Related errors


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