MHSanaei/3x-ui · warning
too many outbounds in one request (max %d)
Error message
too many outbounds in one request (max %d)
What it means
TestOutbounds caps one request at maxBatchItems (50) outbounds to bound the probe batch: each item gets its own loopback port and inbounds entry in a shared xray test process. Exceeding the cap fails the whole request before any probing starts.
Source
Thrown at internal/web/service/outbound/probe_http.go:129
var ob map[string]any
if err := json.Unmarshal([]byte(outboundJSON), &ob); err != nil {
return &TestOutboundResult{Mode: probeModeLabel(mode), Success: false, Error: fmt.Sprintf("Invalid outbound JSON: %v", err)}, nil
}
results := s.testOutboundsParsed([]map[string]any{ob}, testURL, allOutboundsJSON, mode)
return results[0], nil
}
// TestOutbounds probes a JSON array of outbounds and returns one result per
// input, in input order, each carrying the outbound's tag. allOutboundsJSON
// supplies the config context (sockopt.dialerProxy chains); testURL falls
// back to the default probe URL when empty.
func (s *OutboundService) TestOutbounds(outboundsJSON string, testURL string, allOutboundsJSON string, mode string) ([]*TestOutboundResult, error) {
var raw []json.RawMessage
if err := json.Unmarshal([]byte(outboundsJSON), &raw); err != nil {
return nil, fmt.Errorf("invalid outbounds JSON: %w", err)
}
if len(raw) > maxBatchItems {
return nil, fmt.Errorf("too many outbounds in one request (max %d)", maxBatchItems)
}
items := make([]map[string]any, len(raw))
for i, r := range raw {
var ob map[string]any
if err := json.Unmarshal(r, &ob); err == nil {
items[i] = ob
}
}
return s.testOutboundsParsed(items, testURL, allOutboundsJSON, mode), nil
}
// testOutboundsParsed splits items into the TCP lane (direct dials, bounded
// worker pool) and the HTTP lane (one shared temp xray instance), runs both,
// and returns results aligned with items. A nil item marks unparseable input.
func (s *OutboundService) testOutboundsParsed(items []map[string]any, testURL string, allOutboundsJSON string, mode string) []*TestOutboundResult {
results := make([]*TestOutboundResult, len(items))
modeLabel := probeModeLabel(mode)View on GitHub (pinned to ad32144c42)
Solutions
- Split the request into chunks of at most 50 outbounds and aggregate the results client-side
- Filter to only the outbounds you actually need tested before batching
Example fix
// before
results, err := svc.TestOutbounds(allOutboundsJSON, url, ctx, mode) // 120 items
// after
const chunk = 50
for i := 0; i < len(items); i += chunk {
end := min(i+chunk, len(items))
part, _ := svc.TestOutbounds(marshal(items[i:end]), url, ctx, mode)
results = append(results, part...)
} Defensive patterns
Strategy: validation
Validate before calling
const maxBatch = 50
var items []json.RawMessage
_ = json.Unmarshal([]byte(outboundsJSON), &items)
if len(items) > maxBatch {
// split client-side before calling
outboundsJSON = marshalN(items, maxBatch)
}
results, err := svc.TestOutbounds(outboundsJSON, ...) Try / catch
results, err := svc.TestOutbounds(payload, ...)
if err != nil && strings.Contains(err.Error(), "too many outbounds") {
// chunk and retry; aggregate results
} Prevention
- Frontend: disable 'test all' above 50 items or chunk automatically
- Keep the maxBatchItems constant in sync with UI copy
When it happens
Trigger: Sending an array of 51+ outbounds to the batch test endpoint/API in one call.
Common situations: Selecting 'test all' on a large outbound list where the client batches everything into one request; migrating a big config and probing it wholesale.
Related errors
- invalid outbounds JSON: %w
- outbound subscription response body exceeds size limit (limi
- The current username or password is invalid
- The new username and password are empty
- telegram bot disabled
AI-assisted analysis of MHSanaei/3x-ui@ad32144c42 (2026-08-15).
Data as JSON: /api/errors/861242cf886534ba.
Report an issue: GitHub.