XTLS/Xray-core · error

no mux client worker available

Error message

no mux client worker available

What it means

StaticMuxPicker.PickAvailable found workers in the pool but none usable: every registered PortalWorker was skipped because w.draining is true (worker closing, removed after inactivity/24h timer) or w.IsFull() is true (mux worker hit its connection/cool-down capacity, hysteresis full state). Unlike error 93, the bridge tunnels exist — they are just saturated or draining.

Source

Thrown at app/reverse/portal.go:214

	}

	if minIdx == -1 {
		for i, w := range p.workers {
			if w.IsFull() {
				continue
			}
			if w.client.ActiveConnections() < minConn {
				minConn = w.client.ActiveConnections()
				minIdx = i
			}
		}
	}

	if minIdx != -1 {
		return p.workers[minIdx].client, nil
	}

	return nil, errors.New("no mux client worker available")
}

func (p *StaticMuxPicker) AddWorker(worker *PortalWorker) {
	p.access.Lock()
	defer p.access.Unlock()

	p.workers = append(p.workers, worker)
}

type PortalWorker struct {
	client   *mux.ClientWorker
	control  *task.Periodic
	writer   buf.Writer
	reader   buf.Reader
	draining bool
	counter  uint32
	timer    *signal.ActivityTimer
}

View on GitHub (pinned to 7d214f8b09)

Solutions

  1. Add more bridges in the reverse config so the picker can spread connections and always find a non-full worker.
  2. Reduce multiplexing pressure: lower concurrent tunneled connections, or route bulk traffic outside the tunnel.
  3. If it appears right after restarts, wait for the bridge to re-register a fresh worker (monitor interval ~2s) and confirm errors 91-95 are not firing.

Example fix

// before
"reverse": { "portals": [ { "tag": "portal", "domain": "svc.reverse.internal" } ],
            "bridges": [ { "tag": "bridge-1", "domain": "svc.reverse.internal" } ] }

// after: add capacity
"reverse": { "portals": [ { "tag": "portal", "domain": "svc.reverse.internal" } ],
            "bridges": [ { "tag": "bridge-1", "domain": "svc.reverse.internal" },
                          { "tag": "bridge-2", "domain": "svc2.reverse.internal" } ] }
Defensive patterns

Strategy: fallback

Validate before calling

// Capacity pre-check: at least one non-draining, non-full worker
if !picker.HasAvailable() { // iterate workers: !draining && !IsFull()
    return routeViaAlternatePath(ctx, link)
}

Type guard

func pickerHasCapacity(picker reverse.WorkerCounter) bool {
    return picker.HasAvailable() // false when all workers draining or IsFull()
}

Try / catch

if err := dispatch(ctx, link); err != nil {
    if strings.Contains(err.Error(), "no mux client worker available") {
        return retryWithBackoff(ctx, link, attempts=3) // workers free up / re-register quickly
    }
    return err
}

Prevention

When it happens

Trigger: All workers simultaneously in draining or IsFull state at pick time: a burst of tunneled connections filling each mux worker's connection limit, or a mass reconnect draining old workers while new ones have not been added yet.

Common situations: Heavy load through a single bridge connection (all tunneled streams multiplexed over it until capacity), bridge reconnect storms marking old workers draining, or too few bridges for the offered load. Users see intermittent 'no mux client worker available' during peaks.

Related errors


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