netbirdio/netbird · error

session_idle_timeout must be positive for L4 services

Error message

session_idle_timeout must be positive for L4 services

What it means

Returned by validateL4Target when target.options.session_idle_timeout is negative. Despite the message saying 'must be positive', the code only rejects values below zero - zero is the accepted 'unset, use proxy default' state. time.Duration fields serialize as nanosecond integers in JSON/GORM, so any negative number (e.g. -1 for 'infinite') triggers this.

Source

Thrown at management/internals/modules/reverseproxy/service/service.go:1060

	case TargetTypePeer, TargetTypeHost, TargetTypeDomain:
		if err := validateDirectUpstreamHost(0, target); err != nil {
			return err
		}
	case TargetTypeSubnet:
		if target.Host == "" {
			return errors.New("target host is required for subnet targets")
		}
	case TargetTypeCluster:
		// target_id carries the cluster address; the proxy resolves
		// the upstream at request time.
	default:
		return fmt.Errorf("invalid target_type %q for L4 service", target.TargetType)
	}
	if target.Path != nil && *target.Path != "" && *target.Path != "/" {
		return errors.New("path is not supported for L4 services")
	}
	if target.Options.SessionIdleTimeout < 0 {
		return errors.New("session_idle_timeout must be positive for L4 services")
	}
	if target.Options.RequestTimeout < 0 {
		return errors.New("request_timeout must be positive for L4 services")
	}
	if target.Options.SkipTLSVerify {
		return errors.New("skip_tls_verify is not supported for L4 services")
	}
	if target.Options.PathRewrite != "" {
		return errors.New("path_rewrite is not supported for L4 services")
	}
	if len(target.Options.CustomHeaders) > 0 {
		return errors.New("custom_headers is not supported for L4 services")
	}
	return nil
}

// Service mode constants.
const (

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Set session_idle_timeout to 0 (or omit it) to use the proxy default.
  2. Pick an explicit positive duration if you want idle connections closed after a known time.
  3. If 'infinite idle' is the goal, check the proxy's documented maximum/zero semantics - do not encode it as a negative number.

Example fix

// before
{ "target_type": "peer", "target_id": "peer-a", "port": 22,
  "options": { "session_idle_timeout": -1 } }

// after
{ "target_type": "peer", "target_id": "peer-a", "port": 22,
  "options": { "session_idle_timeout": 0 } }
Defensive patterns

Strategy: validation

Validate before calling

func checkL4Timeouts(o TargetOptions) error {
	if o.SessionIdleTimeout < 0 {
		return errors.New("session_idle_timeout must be >= 0 (0 = default)")
	}
	return nil
}

Type guard

func isL4IdleTimeoutValid(o TargetOptions) bool {
	return o.SessionIdleTimeout >= 0
}

Try / catch

if err := svc.Validate(); err != nil {
	if strings.Contains(err.Error(), "session_idle_timeout") {
		return respondBadRequest(errors.New("use 0 for default or a positive duration; negatives are invalid"))
	}
	return respondBadRequest(err)
}

Prevention

When it happens

Trigger: An L4 target with "session_idle_timeout": -1 or a negative Go duration like -30s in the request; arithmetic on durations (timeout - grace) accidentally producing a negative result.

Common situations: Using -1 to mean 'no timeout', a convention from other proxies (nginx-style) that this API does not accept. Client code computing a timeout as a difference of two timestamps that can go negative. Copying a duration from a config where negative meant disabled.

Understand the failure class

Related errors


AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16). Data as JSON: /api/errors/b03fabe8c91f851d. Report an issue: GitHub.