{"record":{"id":"b03fabe8c91f851d","repo":"netbirdio/netbird","slug":"session-idle-timeout-must-be-positive-for-l4-servi","errorCode":null,"errorMessage":"session_idle_timeout must be positive for L4 services","messagePattern":"session_idle_timeout must be positive for L4 services","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"error","filePath":"management/internals/modules/reverseproxy/service/service.go","lineNumber":1060,"sourceCode":"\tcase TargetTypePeer, TargetTypeHost, TargetTypeDomain:\n\t\tif err := validateDirectUpstreamHost(0, target); err != nil {\n\t\t\treturn err\n\t\t}\n\tcase TargetTypeSubnet:\n\t\tif target.Host == \"\" {\n\t\t\treturn errors.New(\"target host is required for subnet targets\")\n\t\t}\n\tcase TargetTypeCluster:\n\t\t// target_id carries the cluster address; the proxy resolves\n\t\t// the upstream at request time.\n\tdefault:\n\t\treturn fmt.Errorf(\"invalid target_type %q for L4 service\", target.TargetType)\n\t}\n\tif target.Path != nil && *target.Path != \"\" && *target.Path != \"/\" {\n\t\treturn errors.New(\"path is not supported for L4 services\")\n\t}\n\tif target.Options.SessionIdleTimeout < 0 {\n\t\treturn errors.New(\"session_idle_timeout must be positive for L4 services\")\n\t}\n\tif target.Options.RequestTimeout < 0 {\n\t\treturn errors.New(\"request_timeout must be positive for L4 services\")\n\t}\n\tif target.Options.SkipTLSVerify {\n\t\treturn errors.New(\"skip_tls_verify is not supported for L4 services\")\n\t}\n\tif target.Options.PathRewrite != \"\" {\n\t\treturn errors.New(\"path_rewrite is not supported for L4 services\")\n\t}\n\tif len(target.Options.CustomHeaders) > 0 {\n\t\treturn errors.New(\"custom_headers is not supported for L4 services\")\n\t}\n\treturn nil\n}\n\n// Service mode constants.\nconst (","sourceCodeStart":1042,"sourceCodeEnd":1078,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/management/internals/modules/reverseproxy/service/service.go#L1042-L1078","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\n{ \"target_type\": \"peer\", \"target_id\": \"peer-a\", \"port\": 22,\n  \"options\": { \"session_idle_timeout\": -1 } }\n\n// after\n{ \"target_type\": \"peer\", \"target_id\": \"peer-a\", \"port\": 22,\n  \"options\": { \"session_idle_timeout\": 0 } }","handlingStrategy":"validation","validationCode":"func checkL4Timeouts(o TargetOptions) error {\n\tif o.SessionIdleTimeout < 0 {\n\t\treturn errors.New(\"session_idle_timeout must be >= 0 (0 = default)\")\n\t}\n\treturn nil\n}","typeGuard":"func isL4IdleTimeoutValid(o TargetOptions) bool {\n\treturn o.SessionIdleTimeout >= 0\n}","tryCatchPattern":"if err := svc.Validate(); err != nil {\n\tif strings.Contains(err.Error(), \"session_idle_timeout\") {\n\t\treturn respondBadRequest(errors.New(\"use 0 for default or a positive duration; negatives are invalid\"))\n\t}\n\treturn respondBadRequest(err)\n}","preventionTips":["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."],"tags":["netbird","reverse-proxy","validation","l4","timeout","configuration","go"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}