XTLS/Xray-core · error

portal domain is empty

Error message

portal domain is empty

What it means

Reverse-proxy portal constructor validation, checked right after the tag: the portal needs the domain under which tunneled targets are addressed, because HandleConnection matches the outbound target against p.domain (isDomain) to decide whether to spin up a mux client for this connection. An empty domain makes that matching meaningless, so NewPortal rejects it.

Source

Thrown at app/reverse/portal.go:37

	"github.com/xtls/xray-core/transport/pipe"
	"google.golang.org/protobuf/proto"
)

type Portal struct {
	ohm    outbound.Manager
	tag    string
	domain string
	picker *StaticMuxPicker
	client *mux.ClientManager
}

func NewPortal(config *PortalConfig, ohm outbound.Manager) (*Portal, error) {
	if config.Tag == "" {
		return nil, errors.New("portal tag is empty")
	}

	if config.Domain == "" {
		return nil, errors.New("portal domain is empty")
	}

	picker, err := NewStaticMuxPicker()
	if err != nil {
		return nil, err
	}

	return &Portal{
		ohm:    ohm,
		tag:    config.Tag,
		domain: config.Domain,
		picker: picker,
		client: &mux.ClientManager{
			Picker: picker,
		},
	}, nil
}

View on GitHub (pinned to 7d214f8b09)

Solutions

  1. Set "domain" on the portal entry to the same value used by the corresponding bridge.
  2. Keep bridge and portal domain pairs in sync when editing either side of the tunnel.
  3. Use a clearly reserved internal domain suffix for reverse traffic to avoid intercepting real hostnames.

Example fix

// before
"reverse": { "portals": [ { "tag": "portal" } ] }

// after
"reverse": { "portals": [ { "tag": "portal", "domain": "svc.reverse.internal" } ] }
Defensive patterns

Strategy: validation

Validate before calling

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

Type guard

func validPortalDomain(p PortalConfig) bool { return p.Domain != "" }

Try / catch

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

Prevention

When it happens

Trigger: A portals[] entry in the reverse config with a missing or empty "domain"; fires during instance start after the tag check succeeds.

Common situations: Same family as the other reverse validation errors: omitted domain when editing configs, mismatched field casing, or a domain intended only on the bridge side. Startup aborts with this error.

Related errors


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