{"record":{"id":"73dad979c48c06d9","repo":"netbirdio/netbird","slug":"listen-port-is-not-supported-for-http-services","errorCode":null,"errorMessage":"listen_port is not supported for HTTP services","messagePattern":"listen_port is not supported for HTTP services","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"error","filePath":"management/internals/modules/reverseproxy/service/service.go","lineNumber":915,"sourceCode":"\t}\n\tif s.Mode != \"\" && s.Mode != ModeHTTP {\n\t\treturn fmt.Errorf(\"private services only support HTTP mode, got %q\", s.Mode)\n\t}\n\tif len(s.AccessGroups) == 0 {\n\t\treturn errors.New(\"private services require at least one access group\")\n\t}\n\tif s.Auth.BearerAuth != nil && s.Auth.BearerAuth.Enabled {\n\t\treturn errors.New(\"private services cannot enable bearer auth (SSO): NetBird-only access and SSO are mutually exclusive\")\n\t}\n\treturn nil\n}\n\nfunc (s *Service) validateHTTPMode() error {\n\tif s.Domain == \"\" {\n\t\treturn errors.New(\"service domain is required\")\n\t}\n\tif s.ListenPort != 0 {\n\t\treturn errors.New(\"listen_port is not supported for HTTP services\")\n\t}\n\treturn s.validateHTTPTargets()\n}\n\nfunc (s *Service) validateTCPUDPMode() error {\n\tif s.Domain == \"\" {\n\t\treturn errors.New(\"domain is required for TCP/UDP services (used for cluster derivation)\")\n\t}\n\tif s.isAuthEnabled() {\n\t\treturn errors.New(\"auth is not supported for TCP/UDP services\")\n\t}\n\tif len(s.Targets) != 1 {\n\t\treturn errors.New(\"TCP/UDP services must have exactly one target\")\n\t}\n\tif s.Mode == ModeUDP && s.Targets[0].ProxyProtocol {\n\t\treturn errors.New(\"proxy_protocol is not supported for UDP services\")\n\t}\n\treturn s.validateL4Target(s.Targets[0])","sourceCodeStart":897,"sourceCodeEnd":933,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/management/internals/modules/reverseproxy/service/service.go#L897-L933","documentation":"Returned by Service.Validate() -> validateHTTPMode when the service mode resolves to \"http\" (an empty Mode is defaulted to http by Validate()) and Service.ListenPort is non-zero. HTTP reverse-proxy services are routed by hostname on the proxy's shared listeners, so a dedicated listen port has no meaning there; listen ports belong to the L4 modes (required for tls, optional-but-routed for tcp/udp). The check fails fast at API/store time so the invalid combination never reaches the proxy config push.","triggerScenarios":"POST/PUT of a reverse-proxy service with mode \"http\" or mode omitted and a non-zero listen_port in the payload; updating an existing TCP service to mode http without clearing listen_port; a UI or Terraform-like module that always serializes a listen_port field (e.g. 443) even for HTTP services.","commonSituations":"Copying a TCP/TLS service JSON as a template for a new HTTP service and forgetting to delete listen_port. Frontend forms that submit every field with a default port value. Migrating a service between modes during a restructure and leaving stale fields behind.","solutions":["Remove listen_port (omit the field or set it to 0) from the HTTP service definition.","If you actually want port-based routing, change mode to \"tcp\" or \"udp\" and keep the port.","If you are terminating TLS on a dedicated port with SNI routing, use mode \"tls\" which requires listen_port.","If a UI/automation always sends the field, patch it to omit listen_port when mode is http."],"exampleFix":"// before\n{\n  \"name\": \"my-app\",\n  \"mode\": \"http\",\n  \"domain\": \"netbird.example.com\",\n  \"listen_port\": 8443,\n  \"targets\": [...]\n}\n\n// after\n{\n  \"name\": \"my-app\",\n  \"mode\": \"http\",\n  \"domain\": \"netbird.example.com\",\n  \"targets\": [...]\n}","handlingStrategy":"validation","validationCode":"func checkHTTPListenPort(mode string, listenPort uint16) error {\n\tif mode == \"\" {\n\t\tmode = \"http\"\n\t}\n\tif mode == \"http\" && listenPort != 0 {\n\t\treturn fmt.Errorf(\"listen_port %d not allowed for http service\", listenPort)\n\t}\n\treturn nil\n}","typeGuard":"func isHTTPModeSafe(mode string, listenPort uint16) bool {\n\tif mode == \"\" {\n\t\tmode = \"http\"\n\t}\n\treturn mode != \"http\" || listenPort == 0\n}","tryCatchPattern":"if err := svc.Validate(); err != nil {\n\tif strings.Contains(err.Error(), \"listen_port is not supported for HTTP\") {\n\t\t// config bug, fix payload - never retry\n\t}\n\treturn respondBadRequest(err)\n}","preventionTips":["Mode-specific payload builders: only attach listen_port when mode is tcp, udp, or tls.","Treat validation errors from Validate() as permanent 400s - never retry them.","Add a schema/fixture test that round-trips each mode's payload through Validate()."],"tags":["netbird","reverse-proxy","validation","configuration","http","go"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}