{"record":{"id":"84a5988a7b4259fd","repo":"netbirdio/netbird","slug":"tls-services-must-have-exactly-one-target","errorCode":null,"errorMessage":"TLS services must have exactly one target","messagePattern":"TLS services must have exactly one target","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"error","filePath":"management/internals/modules/reverseproxy/service/service.go","lineNumber":947,"sourceCode":"\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])\n}\n\nfunc (s *Service) validateTLSMode() error {\n\tif s.Domain == \"\" {\n\t\treturn errors.New(\"domain is required for TLS services (used for SNI matching)\")\n\t}\n\tif s.isAuthEnabled() {\n\t\treturn errors.New(\"auth is not supported for TLS services\")\n\t}\n\tif s.ListenPort == 0 {\n\t\treturn errors.New(\"listen_port is required for TLS services\")\n\t}\n\tif len(s.Targets) != 1 {\n\t\treturn errors.New(\"TLS services must have exactly one target\")\n\t}\n\treturn s.validateL4Target(s.Targets[0])\n}\n\nfunc (s *Service) validateHTTPTargets() error {\n\tfor i, target := range s.Targets {\n\t\tswitch target.TargetType {\n\t\tcase TargetTypePeer, TargetTypeHost, TargetTypeDomain:\n\t\t\t// Host is normally overwritten by replaceHostByLookup with the\n\t\t\t// resolved peer IP / resource address; operator-supplied values\n\t\t\t// are honored only when DirectUpstream is set. Validate the\n\t\t\t// override here so misconfigured hosts fail fast at API time.\n\t\t\tif err := validateDirectUpstreamHost(i, target); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase TargetTypeSubnet:\n\t\t\tif target.Host == \"\" {\n\t\t\t\treturn fmt.Errorf(\"target %d has empty host but target_type is %q\", i, target.TargetType)","sourceCodeStart":929,"sourceCodeEnd":965,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/management/internals/modules/reverseproxy/service/service.go#L929-L965","documentation":"Returned by validateTLSMode when len(s.Targets) != 1. A TLS service passes the decrypted-byte stream (post SNI match) to exactly one upstream; multiple targets have no routing key at this layer. Same single-target contract as tcp/udp, and for the same reason.","triggerScenarios":"Submitting a tls service with two or more targets; reusing an HTTP multi-target template for a TLS passthrough service.","commonSituations":"Listing a primary and standby database behind one TLS endpoint expecting failover. Path-routed HTTP habits carried into tls mode.","solutions":["Keep exactly one target on the tls service.","Create separate tls services (each with its own domain/listen_port) per upstream.","If multiple backends behind one hostname are the goal, use mode http where multi-target path routing exists."],"exampleFix":"// before\n{ \"mode\": \"tls\", \"listen_port\": 4433, \"targets\": [\n  { \"target_id\": \"peer-a\", \"port\": 443 }, { \"target_id\": \"peer-b\", \"port\": 443 } ] }\n\n// after\n{ \"mode\": \"tls\", \"listen_port\": 4433, \"targets\": [ { \"target_id\": \"peer-a\", \"port\": 443 } ] }","handlingStrategy":"validation","validationCode":"func checkTLSSingleTarget(mode string, targets []Target) error {\n\tif mode == \"tls\" && len(targets) != 1 {\n\t\treturn fmt.Errorf(\"tls needs exactly 1 target, got %d\", len(targets))\n\t}\n\treturn nil\n}","typeGuard":"func isTLSTargetCountValid(mode string, targets []Target) bool {\n\treturn mode != \"tls\" || len(targets) == 1\n}","tryCatchPattern":"if err := svc.Validate(); err != nil {\n\tif strings.Contains(err.Error(), \"TLS services must have exactly one target\") {\n\t\treturn respondBadRequest(errors.New(\"one tls service per upstream\"))\n\t}\n\treturn respondBadRequest(err)\n}","preventionTips":["Treat all L4 modes (tcp/udp/tls) as single-upstream contracts.","Generate one service object per upstream in automation rather than appending targets.","Validate target cardinality client-side per mode to catch it before the round trip."],"tags":["netbird","reverse-proxy","validation","tls","targets","l4","go"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}