XTLS/Xray-core · error

failed to create portal worker

Error message

failed to create portal worker

What it means

Portal-side failure one step after mux client creation: NewPortalWorker(muxClient) failed. The constructor (see error 95) dispatches the internal control connection over the mux client (to the internal domain destination) and returns 'unable to dispatch control connection' when client.Dispatch returns false; that inner error is wrapped here. Without the control connection the worker cannot register/heartbeat, so the whole portal connection is rejected.

Source

Thrown at app/reverse/portal.go:82

	return p.ohm.RemoveHandler(context.Background(), p.tag)
}

func (p *Portal) HandleConnection(ctx context.Context, link *transport.Link) error {
	outbounds := session.OutboundsFromContext(ctx)
	ob := outbounds[len(outbounds)-1]
	if ob == nil {
		return errors.New("outbound metadata not found").AtError()
	}

	if isDomain(ob.Target, p.domain) {
		muxClient, err := mux.NewClientWorker(*link, mux.ClientStrategy{})
		if err != nil {
			return errors.New("failed to create mux client worker").Base(err).AtWarning()
		}

		worker, err := NewPortalWorker(muxClient)
		if err != nil {
			return errors.New("failed to create portal worker").Base(err)
		}

		p.picker.AddWorker(worker)

		if _, ok := link.Reader.(*pipe.Reader); !ok {
			select {
			case <-ctx.Done():
			case <-muxClient.WaitClosed():
			}
		}
		return nil
	}

	if ob.Target.Network == net.Network_UDP && ob.OriginalTarget.Address != nil && ob.OriginalTarget.Address != ob.Target.Address {
		link.Reader = &buf.EndpointOverrideReader{Reader: link.Reader, Dest: ob.Target.Address, OriginalDest: ob.OriginalTarget.Address}
		link.Writer = &buf.EndpointOverrideWriter{Writer: link.Writer, Dest: ob.Target.Address, OriginalDest: ob.OriginalTarget.Address}
	}

View on GitHub (pinned to 7d214f8b09)

Solutions

  1. Treat as transient first: the bridge's periodic monitor reconnects every few seconds; check whether errors persist or self-heal.
  2. Upgrade both portal and bridge instances to matching xray-core versions so mux control semantics agree.
  3. If persistent, inspect carrier link health (TLS, keepalives, MTU) between the two sides — a dying carrier kills the control dispatch.
Defensive patterns

Strategy: retry

Validate before calling

null // nothing caller-side prevents a control-dispatch failure inside the worker ctor

Try / catch

if err := portal.HandleConnection(ctx, link); err != nil {
    if strings.Contains(err.Error(), "failed to create portal worker") {
        return scheduleBridgeReconnect(2 * time.Second) // monitor interval; treat as transient
    }
    return err
}

Prevention

When it happens

Trigger: The freshly created mux.ClientWorker refuses the control-connection dispatch — typically because the worker is already closed/full or its internal state rejected the new stream during tunnel setup.

Common situations: Bridge/portal version mismatch changing mux behavior, the carrier connection dropping microseconds after acceptance (worker closes before control connect), or resource limits where the worker immediately reports full. Usually transient and retried by the next bridge connection.

Related errors


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