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
- Set "domain" on the portal entry to the same value used by the corresponding bridge.
- Keep bridge and portal domain pairs in sync when editing either side of the tunnel.
- 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
- Keep portal.domain and the matching bridge.domain identical in config
- Use a dedicated reserved domain suffix for reverse tunnels
- Run -test validation before deploying reverse configs
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
- bridge tag is empty
- bridge domain is empty
- portal tag is empty
- unknown action: {}
- rCode out of range: {}
AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15).
Data as JSON: /api/errors/a1d7a5f5a133ba51.
Report an issue: GitHub.