{"record":{"id":"865e407f98946e05","repo":"XTLS/Xray-core","slug":"bridge-domain-is-empty","errorCode":null,"errorMessage":"bridge domain is empty","messagePattern":"bridge domain is empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/reverse/bridge.go","lineNumber":34,"sourceCode":"\t\"google.golang.org/protobuf/proto\"\n)\n\n// Bridge is a component in reverse proxy, that relays connections from Portal to local address.\ntype Bridge struct {\n\tdispatcher  routing.Dispatcher\n\ttag         string\n\tdomain      string\n\tworkers     []*BridgeWorker\n\tmonitorTask *task.Periodic\n}\n\n// NewBridge creates a new Bridge instance.\nfunc NewBridge(config *BridgeConfig, dispatcher routing.Dispatcher) (*Bridge, error) {\n\tif config.Tag == \"\" {\n\t\treturn nil, errors.New(\"bridge tag is empty\")\n\t}\n\tif config.Domain == \"\" {\n\t\treturn nil, errors.New(\"bridge domain is empty\")\n\t}\n\n\tb := &Bridge{\n\t\tdispatcher: dispatcher,\n\t\ttag:        config.Tag,\n\t\tdomain:     config.Domain,\n\t}\n\tb.monitorTask = &task.Periodic{\n\t\tExecute:  b.monitor,\n\t\tInterval: time.Second * 2,\n\t}\n\treturn b, nil\n}\n\nfunc (b *Bridge) cleanup() {\n\tvar activeWorkers []*BridgeWorker\n\n\tfor _, w := range b.workers {","sourceCodeStart":16,"sourceCodeEnd":52,"githubUrl":"https://github.com/XTLS/Xray-core/blob/7d214f8b094f75322fa3990f8aadad1c912f24f5/app/reverse/bridge.go#L16-L52","documentation":"Reverse-proxy bridge constructor validation, the sibling of the tag check: each Bridge must advertise the domain it serves, because the portal side matches connection targets against that domain to decide which bridge a connection belongs to. NewBridge returns this error when BridgeConfig.Domain is empty.","triggerScenarios":"A reverse config bridges[] entry with a missing or empty \"domain\" field; the check fires after the tag check passes, during Bridge construction at instance start.","commonSituations":"Omitting the domain when writing a reverse tunnel config; using an IP or port string where a domain is expected and accidentally clearing the field; converting configs between formats and dropping the domain key. Startup aborts.","solutions":["Set a non-empty \"domain\" on the bridge entry; it must equal the domain the portal side uses for this bridge.","Ensure the same domain appears in the matching portal config so isDomain matching on the portal side works.","Prefer a dedicated reserved domain (e.g. *.reverse.internal) to avoid clashing with real DNS names."],"exampleFix":"// before\n\"reverse\": { \"bridges\": [ { \"tag\": \"bridge-1\" } ] }\n\n// after\n\"reverse\": { \"bridges\": [ { \"tag\": \"bridge-1\", \"domain\": \"svc.reverse.internal\" } ] }","handlingStrategy":"validation","validationCode":"for _, b := range cfg.Reverse.Bridges {\n    if b.Domain == \"\" { return errors.New(\"reverse config: bridge domain is empty\") }\n}","typeGuard":"func validBridgeDomain(b BridgeConfig) bool { return b.Domain != \"\" }","tryCatchPattern":"if _, err := reverse.NewBridge(cfg, dispatcher); err != nil {\n    if strings.Contains(err.Error(), \"bridge domain is empty\") {\n        return fmt.Errorf(\"reverse.bridges[%d]: domain required\", i)\n    }\n    return err\n}","preventionTips":["Template bridges as {tag, domain} units so one is never emitted without the other","Use a reserved internal suffix for reverse domains","Validate config with xray run -test in deployment scripts"],"tags":["go","xray","reverse-proxy","config","validation"],"backgroundTag":null,"analyzedSha":"7d214f8b094f75322fa3990f8aadad1c912f24f5","analyzedAt":"2026-08-15T14:26:24.325Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}