XTLS/Xray-core · error

failed to read HTTP response

Error message

failed to read HTTP response

What it means

Raised after a successful 200 response when buf.ReadAllToBytes fails while draining resp.Body. This means the connection broke mid-transfer: the server (or an intermediary) closed the stream early, the read exceeded the 30-second client timeout, or the body was truncated. The underlying error is chained with Base(err).

Source

Thrown at main/confloader/external/external.go:92

	}

	resp, err := client.Do(&http.Request{
		Method: "GET",
		URL:    parsedTarget,
		Close:  true,
	})
	if err != nil {
		return nil, errors.New("failed to dial to ", target).Base(err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		return nil, errors.New("unexpected HTTP status code: ", resp.StatusCode)
	}

	content, err := buf.ReadAllToBytes(resp.Body)
	if err != nil {
		return nil, errors.New("failed to read HTTP response").Base(err)
	}

	return content, nil
}

// isRemoteSource reports whether arg should be fetched via HTTP (regular
// network or Unix socket) rather than read from the local filesystem.
// Recognized forms:
//
//   - http(s)://...           regular HTTP(S)
//   - @abstract[:/api]        abstract socket (Linux/Android)
//   - /abs/path:/api          filesystem socket, explicit HTTP path
//   - /abs/path               filesystem socket detected via os.ModeSocket
func isRemoteSource(arg string) bool {
	if arg == "" {
		return false
	}
	if strings.HasPrefix(arg, "http://") || strings.HasPrefix(arg, "https://") {

View on GitHub (pinned to 7d214f8b09)

Solutions

  1. Check the chained Base error: 'unexpected EOF' means truncated body, 'context deadline exceeded' means the 30s timeout hit
  2. Retry the fetch — transient truncation often resolves itself
  3. Reduce response size or speed up the server so the full body streams well under 30 seconds
  4. If the timeout is the persistent problem, fetch the content externally and pass a local file to Xray

Example fix

// before: remote fetch raced a 30s timeout
err := confloader.ExtLoadConfig("http://slow-host/big-config.json")

// after: pre-fetch with a longer timeout, then load locally
resp, err := (&http.Client{Timeout: 5 * time.Minute}).Get(url) // save to file...
err = confloader.ExtLoadConfig("/tmp/big-config.json")
Defensive patterns

Strategy: retry

Validate before calling

// fetch with your own client and a generous timeout, verify length, then pass local file
c := &http.Client{Timeout: 5 * time.Minute}
if b, err := c.Get(url); err == nil && b.ContentLength > 0 { /* cache to disk, load from disk */ }

Try / catch

if errors.Is(err, io.ErrUnexpectedEOF) || strings.Contains(err.Error(), "unexpected EOF") { retryWithBackoff() }

Prevention

When it happens

Trigger: Server closes the connection partway through sending the config; network drops the TCP connection; a slow config endpoint pushes a large body past the fixed 30s http.Client timeout; socket-side server crashes mid-write.

Common situations: Large config files served by slow dynamic endpoints (API generating config on the fly), flaky wireless links, proxies/LBs with aggressive idle timeouts, or a Unix-socket API that dies during serialization.

Related errors


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