XTLS/Xray-core · error

bridge domain is empty

Error message

bridge domain is empty

What it means

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.

Source

Thrown at app/reverse/bridge.go:34

	"google.golang.org/protobuf/proto"
)

// Bridge is a component in reverse proxy, that relays connections from Portal to local address.
type Bridge struct {
	dispatcher  routing.Dispatcher
	tag         string
	domain      string
	workers     []*BridgeWorker
	monitorTask *task.Periodic
}

// NewBridge creates a new Bridge instance.
func NewBridge(config *BridgeConfig, dispatcher routing.Dispatcher) (*Bridge, error) {
	if config.Tag == "" {
		return nil, errors.New("bridge tag is empty")
	}
	if config.Domain == "" {
		return nil, errors.New("bridge domain is empty")
	}

	b := &Bridge{
		dispatcher: dispatcher,
		tag:        config.Tag,
		domain:     config.Domain,
	}
	b.monitorTask = &task.Periodic{
		Execute:  b.monitor,
		Interval: time.Second * 2,
	}
	return b, nil
}

func (b *Bridge) cleanup() {
	var activeWorkers []*BridgeWorker

	for _, w := range b.workers {

View on GitHub (pinned to 7d214f8b09)

Solutions

  1. Set a non-empty "domain" on the bridge entry; it must equal the domain the portal side uses for this bridge.
  2. Ensure the same domain appears in the matching portal config so isDomain matching on the portal side works.
  3. Prefer a dedicated reserved domain (e.g. *.reverse.internal) to avoid clashing with real DNS names.

Example fix

// before
"reverse": { "bridges": [ { "tag": "bridge-1" } ] }

// after
"reverse": { "bridges": [ { "tag": "bridge-1", "domain": "svc.reverse.internal" } ] }
Defensive patterns

Strategy: validation

Validate before calling

for _, b := range cfg.Reverse.Bridges {
    if b.Domain == "" { return errors.New("reverse config: bridge domain is empty") }
}

Type guard

func validBridgeDomain(b BridgeConfig) bool { return b.Domain != "" }

Try / catch

if _, err := reverse.NewBridge(cfg, dispatcher); err != nil {
    if strings.Contains(err.Error(), "bridge domain is empty") {
        return fmt.Errorf("reverse.bridges[%d]: domain required", i)
    }
    return err
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15). Data as JSON: /api/errors/865e407f98946e05. Report an issue: GitHub.