XTLS/Xray-core · error
portal tag is empty
Error message
portal tag is empty
What it means
Reverse-proxy portal constructor validation: a Portal (the component on the public/outer side that accepts bridge connections and dispatches traffic into them) must have a tag, since the portal registers itself as an outbound handler under that tag so routing can send traffic to it. NewPortal fails fast when PortalConfig.Tag is empty.
Source
Thrown at app/reverse/portal.go:33
"github.com/xtls/xray-core/common/signal"
"github.com/xtls/xray-core/common/task"
"github.com/xtls/xray-core/features/outbound"
"github.com/xtls/xray-core/transport"
"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,View on GitHub (pinned to 7d214f8b09)
Solutions
- Add a unique "tag" to the portals[] entry (e.g. 'portal').
- Make sure routing rules/balancers that should send traffic through the tunnel reference this exact tag.
- Run xray -test on the config to catch it before deployment.
Example fix
// before
"reverse": { "portals": [ { "domain": "svc.reverse.internal" } ] }
// after
"reverse": { "portals": [ { "tag": "portal", "domain": "svc.reverse.internal" } ] } Defensive patterns
Strategy: validation
Validate before calling
for _, p := range cfg.Reverse.Portals {
if p.Tag == "" { return errors.New("reverse config: portal tag is empty") }
} Type guard
func validPortalConfig(p PortalConfig) bool { return p.Tag != "" } Try / catch
if _, err := reverse.NewPortal(cfg, ohm); err != nil {
if strings.Contains(err.Error(), "portal tag is empty") {
return fmt.Errorf("reverse.portals[%d]: tag required", i)
}
return err
} Prevention
- Emit tag+domain together for portals in templates
- Cross-check the portal tag against routing rules that reference it
- Prefer copy-editing a known-good reverse example
When it happens
Trigger: A reverse config portals[] entry without a "tag" field during Portal construction; the portal later calls ohm.RemoveHandler with this same tag on Close, so an empty tag would corrupt outbound manager state.
Common situations: Hand-authored reverse tunnel configs missing the portal tag; field-name casing mistakes when hand-translating protobuf JSON; editing examples and removing the tag line. Instance startup fails immediately.
Related errors
- bridge tag is empty
- bridge domain is empty
- portal domain is empty
- unknown action: {}
- rCode out of range: {}
AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15).
Data as JSON: /api/errors/443b71f3e0bb614a.
Report an issue: GitHub.