hashicorp/nomad · error

client_introduction.max_identity_ttl must be greater one

Error message

client_introduction.max_identity_ttl must be greater one

What it means

client_introduction validation also requires `max_identity_ttl` to be at least 1, mirroring the default TTL check. A zero or negative maximum identity lifetime is invalid because the agent could never issue any valid identity. This check runs before the max>=default comparison.

Source

Thrown at command/agent/config.go:1272

func (c *ClientIntroduction) Validate() error {

	if c == nil {
		return nil
	}

	if c.Enforcement == "" {
		return errors.New("client_introduction.enforcement must be set")
	}
	if !slices.Contains(ClientIntroductionEnforcementValues, c.Enforcement) {
		return fmt.Errorf("client_introduction.enforcement must be one of %v",
			ClientIntroductionEnforcementValues)
	}

	if c.DefaultIdentityTTL < 1 {
		return errors.New("client_introduction.default_identity_ttl must be greater one")
	}
	if c.MaxIdentityTTL < 1 {
		return errors.New("client_introduction.max_identity_ttl must be greater one")
	}
	if c.MaxIdentityTTL < c.DefaultIdentityTTL {
		return errors.New("client_introduction.max_identity_ttl must be greater than default_identity_ttl")
	}

	return nil
}

// ServerJoin is used in both clients and servers to bootstrap connections to
// servers
type ServerJoin struct {
	// StartJoin is a list of addresses to attempt to join when the
	// agent starts. If Serf is unable to communicate with any of these
	// addresses, then the agent will error and exit.
	StartJoin []string `hcl:"start_join"`

	// RetryJoin is a list of addresses to join with retry enabled, or a single
	// value to find multiple servers using go-discover syntax.

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Set `max_identity_ttl` to a positive duration, e.g. "24h".
  2. Ensure max_identity_ttl >= default_identity_ttl so the subsequent check also passes.
  3. If 0 was meant to signal 'unlimited', use the largest supported duration instead.

Example fix

// before
client_introduction {
  enforcement          = "serve-and-mark"
  default_identity_ttl = "1h"
  max_identity_ttl     = 0
}

// after
client_introduction {
  enforcement          = "serve-and-mark"
  default_identity_ttl = "1h"
  max_identity_ttl     = "24h"
}
Defensive patterns

Strategy: validation

Validate before calling

if c := cfg.ClientIntroduction; c != nil && c.MaxIdentityTTL < 1 {
    return errors.New("client_introduction.max_identity_ttl must be >= 1")
}

Type guard

func validMaxTTL(c *ClientIntroduction) bool { return c != nil && c.MaxIdentityTTL >= 1 }

Prevention

When it happens

Trigger: Config sets `client_introduction.max_identity_ttl` to 0 or a negative value (or parses to < 1) while enforcement is valid and default_identity_ttl passes its own check.

Common situations: Setting max_ttl = 0 intending 'unlimited'; copy/paste dropping the value; unit mismatch making the intended duration parse as zero.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


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