MHSanaei/3x-ui · error

%s %s: %w

Error message

%s %s: %w

What it means

Returned by Remote.do in internal/web/runtime/remote.go when the HTTP request to a remote sub-node fails before a response is received. The message wraps the underlying transport error (connection refused, DNS failure, TLS handshake error, timeout) with the HTTP method and API path that were attempted. It is the master-panel-side signal that the node endpoint at the configured address could not be reached at all.

Source

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

	}
	req.Header.Set("Accept", "application/json")
	if contentType != "" {
		req.Header.Set("Content-Type", contentType)
	}
	if hashHex != "" {
		req.Header.Set(wirecodec.HashHeader, hashHex)
	}
	if zstdEncoded {
		req.Header.Set("Content-Encoding", wirecodec.EncodingZstd)
	}

	client, err := r.httpClient()
	if err != nil {
		return nil, err
	}
	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("%s %s: %w", method, path, err)
	}
	defer resp.Body.Close()
	r.recordCaps(resp.Header)

	// Validate status before reading a success payload: a non-OK response's
	// body is never used beyond a short diagnostic, so don't let a node force us
	// to buffer a large body just to return an HTTP error.
	if resp.StatusCode != http.StatusOK {
		snippet, _ := io.ReadAll(io.LimitReader(resp.Body, errBodyDiagBytes))
		if msg := bytes.TrimSpace(snippet); len(msg) > 0 {
			// %q quotes/escapes the untrusted node body so control characters or
			// newlines in it can't garble or inject into the error/log output.
			return nil, fmt.Errorf("%s %s: HTTP %d: %q", method, path, resp.StatusCode, msg)
		}
		return nil, fmt.Errorf("%s %s: HTTP %d", method, path, resp.StatusCode)
	}

	// Fast-fail on an honestly-declared oversize body; the LimitReader below is

View on GitHub (pinned to ad32144c42)

Solutions

  1. Check the node is running and its panel port is listening: curl -vk https://<node-addr>/panel/ from the master.
  2. Verify the node's address, port and certificate settings in the master's node form match what the node actually serves.
  3. Look at the wrapped cause at the end of the message: 'connection refused' = wrong port/down service, 'certificate is valid for' = hostname/SNI mismatch, 'context deadline exceeded' = timeout — fix accordingly.
  4. If the cause is a timeout on large inbounds, raise the RPC timeout or reduce the payload (fewer clients per inbound) rather than retrying blindly.

Example fix

// before: node registered as http://10.0.0.5:54321 but panel serves TLS
// node form: Address = http://10.0.0.5:54321  -> GET http://10.0.0.5:54321/panel/api/inbounds/list: ...: http: server gave HTTP response to HTTPS client

// after: match the scheme the node actually listens on
// node form: Address = https://10.0.0.5:54321 (and install the node cert) -> RPC succeeds
Defensive patterns

Strategy: retry

Validate before calling

// Before a batch of RPCs, verify the node endpoint answers at all
func nodeReachable(ctx context.Context, nodeURL string) error {
    req, _ := http.NewRequestWithContext(ctx, http.MethodHead, strings.TrimSuffix(nodeURL, "/")+"/", nil)
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return err
    }
    resp.Body.Close()
    return nil
}

Type guard

func isTransportError(err error) bool {
    return err != nil && strings.Contains(err.Error(), ": ") &&
        (errors.Is(err, context.DeadlineExceeded) ||
         errors.Is(err, context.Canceled) ||
         strings.Contains(err.Error(), "connection refused") ||
         strings.Contains(err.Error(), "no such host"))
}

Try / catch

if err := remote.UpdateInbound(ctx, before, after); err != nil {
    if isTransportError(err) { // transient: let reconcile retry
        markNodeDirty(nodeID)
        return nil
    }
    return err
}

Prevention

When it happens

Trigger: Calling any Remote RPC (UpdateInbound, AddClient, refreshRemoteIDs, ListInboundOptions...) while the node process is down, the node address/port is wrong, the panel-to-node URL uses https against a plain-HTTP listener (or vice versa), the mTLS client certificate is rejected, or the request exceeds the client's timeout budget (context deadline).

Common situations: Node server rebooted or x-ui service stopped; firewall/NAT rule dropped after infra change; node FQDN expired or DNS record removed; certificate rotated on the node so the pinned/verified TLS identity no longer matches; slow node so every RPC times out.

Related errors


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