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
- Check the chained Base error: 'unexpected EOF' means truncated body, 'context deadline exceeded' means the 30s timeout hit
- Retry the fetch — transient truncation often resolves itself
- Reduce response size or speed up the server so the full body streams well under 30 seconds
- 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
- Serve configs from fast static endpoints
- Keep config bodies small (split large assets out of config)
- Fall back to the last known-good cached config file on fetch failure
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
- MaxIdleTimeout must be between 4 and 120
- empty HTTP header value: + key
- invalid URL:
- failed to dial to
- unexpected HTTP status code:
AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15).
Data as JSON: /api/errors/b2b9ed2565c42280.
Report an issue: GitHub.