hashicorp/nomad · error

retry config backoff %d is greater than default max_backoff

Error message

retry config backoff %d is greater than default max_backoff %d

What it means

RetryConfig.Validate ensures the retry backoff does not exceed the effective max backoff. When MaxBackoff is nil the library defaults max backoff to config.DefaultRetryMaxBackoff (1 minute), so a user-supplied Backoff larger than that default has no valid ceiling and validation fails with this message.

Source

Thrown at client/config/config.go:848

}

// Validate returns an error if the receiver is nil or empty, or if Backoff
// is greater than  MaxBackoff.
func (rc *RetryConfig) Validate() error {
	// If the config is nil or empty return false so that it is never assigned.
	if rc == nil || rc.IsEmpty() {
		return errors.New("retry config is nil or empty")
	}

	// If Backoff not set, no need to validate
	if rc.Backoff == nil {
		return nil
	}

	// MaxBackoff nil will end up defaulted to 1 minutes. We should validate that
	// the user supplied backoff does not exceed that.
	if rc.MaxBackoff == nil && *rc.Backoff > config.DefaultRetryMaxBackoff {
		return fmt.Errorf("retry config backoff %d is greater than default max_backoff %d", *rc.Backoff, config.DefaultRetryMaxBackoff)
	}

	// MaxBackoff == 0 means backoff is unbounded. No need to validate.
	if rc.MaxBackoff != nil && *rc.MaxBackoff == 0 {
		return nil
	}

	if rc.MaxBackoff != nil && *rc.Backoff > *rc.MaxBackoff {
		return fmt.Errorf("retry config backoff %d is greater than max_backoff %d", *rc.Backoff, *rc.MaxBackoff)
	}

	return nil
}

// Merge merges two RetryConfigs. The passed instance always takes precedence.
func (rc *RetryConfig) Merge(b *RetryConfig) *RetryConfig {
	if rc == nil {
		return b

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Set max_backoff explicitly to a value >= backoff, e.g. backoff="2m" max_backoff="5m"
  2. Lower backoff to at most the 1-minute default max (e.g. "30s") if unbounded growth is not desired
  3. Review recent config/version changes that removed a previously present max_backoff

Example fix

// before
retry {
  backoff = "2m"
}
// after
retry {
  backoff = "2m"
  max_backoff = "5m"
}
Defensive patterns

Strategy: validation

Validate before calling

const defaultMaxBackoff = time.Minute
if rc.Backoff != nil && rc.MaxBackoff == nil && *rc.Backoff > defaultMaxBackoff {
    return fmt.Errorf("backoff %s exceeds default max_backoff %s; set max_backoff", *rc.Backoff, defaultMaxBackoff)
}

Type guard

func backoffWithinDefaultMax(rc *config.RetryConfig, def time.Duration) bool {
    return rc == nil || rc.Backoff == nil || (rc.MaxBackoff != nil) || *rc.Backoff <= def
}

Try / catch

if err := rc.Validate(); err != nil {
    if strings.Contains(err.Error(), "greater than default max_backoff") {
        rc.MaxBackoff = ptr(time.Duration(*rc.Backoff) * 2)
        err = rc.Validate()
    }
    return err
}

Prevention

When it happens

Trigger: Calling RetryConfig.Validate when Backoff is set (e.g. 120000000000 ns = 2m), MaxBackoff is nil, and *Backoff > config.DefaultRetryMaxBackoff, e.g. backoff="2m" with no max_backoff in the config block.

Common situations: retry { backoff = ... } set to a value above 1m without also setting max_backoff in the agent config; copying a retry block that previously had max_backoff into a config where it was dropped; defaults changing between library versions so a formerly valid backoff now exceeds the default.

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/0172956587d716f1. Report an issue: GitHub.