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
- Set session_idle_timeout to 0 (or omit it) to use the proxy default.
- Pick an explicit positive duration if you want idle connections closed after a known time.
- 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
- Clamp computed durations client-side: if d < 0 { d = 0 }.
- Do not port -1-means-disabled conventions from other proxies.
- Remember 0 is valid and means unset - omit the field rather than encoding semantics in a sign.
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
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- request_timeout must be positive for L4 services
- domain is required for TCP/UDP services (used for cluster de
- auth is not supported for TCP/UDP services
- TCP/UDP services must have exactly one target
- target_id is required for L4 services
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/b03fabe8c91f851d.
Report an issue: GitHub.