{"record":{"id":"f873141d7fa11526","repo":"netbirdio/netbird","slug":"custom-headers-is-not-supported-for-l4-services","errorCode":null,"errorMessage":"custom_headers is not supported for L4 services","messagePattern":"custom_headers is not supported for L4 services","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"error","filePath":"management/internals/modules/reverseproxy/service/service.go","lineNumber":1072,"sourceCode":"\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 (\n\tModeHTTP = \"http\"\n\tModeTCP  = \"tcp\"\n\tModeUDP  = \"udp\"\n\tModeTLS  = \"tls\"\n)\n\n// Target protocol constants (URL scheme for backend connections).\nconst (\n\tTargetProtoHTTP  = \"http\"\n\tTargetProtoHTTPS = \"https\"\n\tTargetProtoTCP   = \"tcp\"\n\tTargetProtoUDP   = \"udp\"","sourceCodeStart":1054,"sourceCodeEnd":1090,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/management/internals/modules/reverseproxy/service/service.go#L1054-L1090","documentation":"Returned by validateL4Target when target.options.custom_headers is a non-empty map. Custom headers are injected into HTTP requests the proxy sends upstream; a TCP/UDP/TLS passthrough has no request/response structure to hang headers on. Note this fires even if the map exists but is empty - no, an empty map passes; only len > 0 is rejected.","triggerScenarios":"An L4 target whose options include custom_headers like {\"X-Client\": \"portal\"} copied from an HTTP target; injecting an auth header by habit on a database forward.","commonSituations":"Trying to pass a static API key or tracing header to an upstream that is actually a raw TCP service. Duplicating a fully-populated options block between HTTP and L4 services.","solutions":["Delete the custom_headers map (or leave it empty) on L4 targets.","If the upstream is really HTTP and needs the header, run the service in mode http.","For credentials on L4, have the client send them inside the protocol stream (e.g. DB auth), not as proxy-injected headers."],"exampleFix":"// before\n\"options\": { \"custom_headers\": { \"X-Upstream-Token\": \"abc\" } }\n\n// after\n\"options\": {}","handlingStrategy":"validation","validationCode":"func checkL4NoCustomHeaders(o TargetOptions) error {\n\tif len(o.CustomHeaders) > 0 {\n\t\treturn errors.New(\"custom_headers is http-only; remove it from L4 targets\")\n\t}\n\treturn nil\n}","typeGuard":"func isL4CustomHeadersClean(o TargetOptions) bool {\n\treturn len(o.CustomHeaders) == 0\n}","tryCatchPattern":"if err := svc.Validate(); err != nil {\n\tif strings.Contains(err.Error(), \"custom_headers is not supported for L4\") {\n\t\treturn respondBadRequest(errors.New(\"L4 has no headers; authenticate inside the protocol instead\"))\n\t}\n\treturn respondBadRequest(err)\n}","preventionTips":["Never plan to inject auth headers into a byte-stream forward - it cannot work.","Gate header options in your config schema on mode == http.","Move upstream credentials into the protocol's own handshake (DB users, SASL, etc.)."],"tags":["netbird","reverse-proxy","validation","l4","headers","options","go"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}