MHSanaei/3x-ui · error

remote response exceeds size limit

Error message

remote response exceeds size limit

What it means

errRemoteResponseTooLarge is returned by readCappedBody in internal/web/runtime/remote.go when a sub-node's HTTP response body exceeds maxRemoteResponseBytes (64 MiB). The runtime reads at most limit+1 bytes; seeing one extra byte proves the body is oversize and it aborts instead of buffering a potentially huge or hostile payload. This protects the master panel's memory when talking to remote nodes.

Source

Thrown at internal/web/runtime/remote.go:49

// overhead can outweigh the savings.
const zstdMinBodyBytes = 1024

// maxRemoteResponseBytes caps a single node RPC's response body. It bounds the
// wire/decompressed size of one response — the real guard against a broken or
// hostile node streaming an unbounded body. It is NOT a process-wide memory
// bound: concurrent RPCs and the decoded JSON can each exceed it, so
// endpoint-specific caps and a concurrency budget remain follow-ups. Node
// responses (traffic snapshots, client-IP lists, inbound options) are JSON and
// stay well under it.
const maxRemoteResponseBytes = 64 << 20 // 64 MiB

// errBodyDiagBytes bounds how much of a non-OK error body we read for a
// diagnostic snippet (and to let small-error connections be reused) without
// buffering a potentially huge or hostile error payload.
const errBodyDiagBytes = 8 << 10 // 8 KiB

// errRemoteResponseTooLarge is returned when a node response exceeds the cap.
var errRemoteResponseTooLarge = errors.New("remote response exceeds size limit")

// readCappedBody reads all of r but rejects bodies larger than limit, returning
// errRemoteResponseTooLarge. It reads at most limit+1 bytes so a body of exactly
// limit is accepted and the first oversize byte is detected without buffering
// more.
func readCappedBody(r io.Reader, limit int64) ([]byte, error) {
	raw, err := io.ReadAll(io.LimitReader(r, limit+1))
	if err != nil {
		return nil, err
	}
	if int64(len(raw)) > limit {
		return nil, errRemoteResponseTooLarge
	}
	return raw, nil
}

type envelope struct {
	Success bool            `json:"success"`

View on GitHub (pinned to ad32144c42)

Solutions

  1. Reduce the payload at the node: fewer clients/inbounds per node, or split reporting across nodes
  2. If the deployment legitimately exceeds 64 MiB, raise maxRemoteResponseBytes in remote.go and rebuild (mind master memory: concurrent RPCs each buffer up to the cap)
  3. Check the node's response for a bug/huge expansion (query the node endpoint directly and inspect Content-Length)
  4. Verify master and node run compatible versions of the runtime protocol

Example fix

// before
const maxRemoteResponseBytes = 64 << 20 // 64 MiB

// after (only if the deployment genuinely needs it)
const maxRemoteResponseBytes = 128 << 20 // 128 MiB
Defensive patterns

Strategy: try-catch

Type guard

func isRemoteResponseTooLarge(err error) bool {
    return errors.Is(err, errRemoteResponseTooLarge)
}

Try / catch

data, err := fetchFromNode(node)
if err != nil {
    if errors.Is(err, errRemoteResponseTooLarge) {
        // alert: node payload > 64 MiB; do not retry blindly — inspect the node
    }
    return err
}

Prevention

When it happens

Trigger: A master panel RPC to a sub-node (traffic stats sync, client-IP list, inbound options fetch) whose JSON payload exceeds 64 MiB — typically a node with tens of thousands of clients/inbounds, or a node returning an unexpectedly huge/looping document.

Common situations: Very large multi-node deployments aggregating traffic snapshots; a misbehaving or compromised node replaying an oversized body; node/panel version mismatch where a newer node emits a much larger response format than the master expects.

Related errors


AI-assisted analysis of MHSanaei/3x-ui@ad32144c42 (2026-08-15). Data as JSON: /api/errors/34617b025e72f3c8. Report an issue: GitHub.