{"record":{"id":"80b654c11274b56f","repo":"fatedier/frp","slug":"subdomain-and-custom-domains-should-not-be-both-em","errorCode":null,"errorMessage":"subdomain and custom domains should not be both empty","messagePattern":"subdomain and custom domains should not be both empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/config/v1/validation/proxy.go","lineNumber":76,"sourceCode":"\n\tif c.Plugin.Type != \"\" {\n\t\tif err := ValidateClientPluginOptions(c.Plugin.ClientPluginOptions); err != nil {\n\t\t\treturn fmt.Errorf(\"plugin %s: %v\", c.Plugin.Type, err)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc validateProxyBaseConfigForServer(c *v1.ProxyBaseConfig) error {\n\tif err := ValidateAnnotations(c.Annotations); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc validateDomainConfigForClient(c *v1.DomainConfig) error {\n\tif c.SubDomain == \"\" && len(c.CustomDomains) == 0 {\n\t\treturn errors.New(\"subdomain and custom domains should not be both empty\")\n\t}\n\treturn nil\n}\n\nfunc validateDomainConfigForServer(c *v1.DomainConfig, s *v1.ServerConfig) error {\n\tsubDomainHost := strings.ToLower(s.SubDomainHost)\n\tfor _, domain := range c.CustomDomains {\n\t\tcanonicalDomain := strings.ToLower(domain)\n\t\tif subDomainHost != \"\" && len(strings.Split(subDomainHost, \".\")) < len(strings.Split(canonicalDomain, \".\")) {\n\t\t\tif strings.HasSuffix(canonicalDomain, \".\"+subDomainHost) {\n\t\t\t\treturn fmt.Errorf(\"custom domain [%s] should not belong to subdomain host [%s]\", domain, s.SubDomainHost)\n\t\t\t}\n\t\t}\n\t}\n\n\tif c.SubDomain != \"\" {\n\t\tif s.SubDomainHost == \"\" {\n\t\t\treturn errors.New(\"subdomain is not supported because this feature is not enabled in server\")","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/pkg/config/v1/validation/proxy.go#L58-L94","documentation":"Validation error from validateDomainConfigForClient: an frpc proxy of an HTTP/HTTPS/TCP-mux type has neither subdomain nor customDomains set. These proxies are routed by hostname on frps, so at least one routing domain must be declared client-side. Checked by validateTCPMuxProxyConfigForClient / validateHTTPProxyConfigForClient / validateHTTPSProxyConfigForClient via the shared DomainConfig.","triggerScenarios":"A [[proxies]] entry with type = \"http\" (or https/tcpmux) that declares neither subdomain nor customDomains — e.g. an http proxy copied from a tcp template where only remotePort was set.","commonSituations":"Converting a tcp proxy to http/https and forgetting that remotePort is not used for routing; declaring domains under the wrong table so DomainConfig decodes empty; expecting the server's subdomainHost to provide a default (it does not — a subdomain value is still required).","solutions":["Add either subdomain = \"myapp\" (resolved against frps subDomainHost) or customDomains = [\"app.example.com\"] to the http/https/tcpmux proxy.","If you actually want port-based routing, change type back to \"tcp\" and use remotePort.","For customDomains pointing at the server, ensure DNS for those domains routes to the frps host."],"exampleFix":"# before\n[[proxies]]\nname = \"web\"\ntype = \"http\"\nremotePort = 8080\n\n# after\n[[proxies]]\nname = \"web\"\ntype = \"http\"\nsubdomain = \"web\"","handlingStrategy":"validation","validationCode":"for _, p := range clientCfg.Proxies {\n    switch v := p.(type) {\n    case *v1.HTTPProxyConfig:\n        if v.SubDomain == \"\" && len(v.CustomDomains) == 0 {\n            return errors.New(\"http proxy needs subdomain or customDomains\")\n        }\n    case *v1.HTTPSProxyConfig:\n        if v.SubDomain == \"\" && len(v.CustomDomains) == 0 {\n            return errors.New(\"https proxy needs subdomain or customDomains\")\n        }\n    }\n}","typeGuard":"func domainRouted(d v1.DomainConfig) bool {\n    return d.SubDomain != \"\" || len(d.CustomDomains) > 0\n}","tryCatchPattern":"if err := validation.ValidateProxyConfigurerForClient(p); err != nil {\n    if strings.Contains(err.Error(), \"subdomain and custom domains should not be both empty\") {\n        // add subdomain or customDomains, or switch type to tcp with remotePort\n    }\n    return err\n}","preventionTips":["http/https/tcpmux proxies are routed by hostname: always set subdomain or customDomains.","tcp/udp proxies use remotePort — pick the right type first.","Set up DNS for customDomains to point at the frps host."],"tags":["config","proxy","http","domain","validation","frpc"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}