{"record":{"id":"233f124578452728","repo":"netbirdio/netbird","slug":"domain-is-required-for-tls-services-used-for-sni","errorCode":null,"errorMessage":"domain is required for TLS services (used for SNI matching)","messagePattern":"domain is required for TLS services \\(used for SNI matching\\)","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"error","filePath":"management/internals/modules/reverseproxy/service/service.go","lineNumber":938,"sourceCode":"func (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])\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","sourceCodeStart":920,"sourceCodeEnd":956,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/management/internals/modules/reverseproxy/service/service.go#L920-L956","documentation":"Returned by validateTLSMode when a tls service has an empty Domain. TLS mode routes connections by matching the server_name in the TLS ClientHello (SNI), and the service domain is that match key - without it the proxy cannot decide which service a connection belongs to. This is why the domain is required even though TLS also uses a listen_port.","triggerScenarios":"Creating a tls service supplying only listen_port and a target, with no domain; migrating a tcp service to tls (where domain was set for cluster derivation) and dropping the field in the migration script.","commonSituations":"Assuming the dedicated port alone identifies the service so the domain is redundant. Copying the minimal field set from an L4 example that omitted domain. Automation that treats domain as an HTTP-only attribute.","solutions":["Set the service domain to the hostname clients will put in their TLS SNI, e.g. \"db.netbird.example.com\".","Ensure the domain resolves through your account's zone configuration so certificates/SNI line up.","Verify the API client is not silently dropping the domain field on serialization."],"exampleFix":"// before\n{ \"mode\": \"tls\", \"listen_port\": 853, \"targets\": [ { \"target_id\": \"peer-a\", \"port\": 853 } ] }\n\n// after\n{ \"mode\": \"tls\", \"domain\": \"dns.netbird.example.com\", \"listen_port\": 853, \"targets\": [ { \"target_id\": \"peer-a\", \"port\": 853 } ] }","handlingStrategy":"validation","validationCode":"func checkTLSDomain(mode, domain string) error {\n\tif mode == \"tls\" && domain == \"\" {\n\t\treturn errors.New(\"domain is required for tls services (SNI matching)\")\n\t}\n\treturn nil\n}","typeGuard":"func hasTLSDomain(mode, domain string) bool {\n\treturn mode != \"tls\" || domain != \"\"\n}","tryCatchPattern":"if err := svc.Validate(); err != nil {\n\tif strings.Contains(err.Error(), \"domain is required for TLS\") {\n\t\treturn respondBadRequest(errors.New(\"set domain to the SNI hostname clients will send\"))\n\t}\n\treturn respondBadRequest(err)\n}","preventionTips":["Derive the tls service domain from the hostname you issue certificates for, so SNI always matches.","Require domain in your client struct for every mode - all four modes need it.","Add a smoke test that connects with openssl s_client and checks SNI dispatch after creation."],"tags":["netbird","reverse-proxy","validation","tls","sni","domain","go"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}