XTLS/Xray-core · warning · errors.Error
outbound failed to relay connection
Error message
outbound failed to relay connection
What it means
Thrown in app/observatory/observer.go:179 when the probe HTTP GET (default https://www.google.com/generate_204, or config.ProbeUrl) returns an error from httpClient.Do. Unlike the dialer errors, this covers the whole request lifecycle — dial, TLS handshake (5s timeout), sending, and response — and marks the outbound as failed for this observation round.
Source
Thrown at app/observatory/observer.go:179
Transport: &httpTransport,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
Jar: nil,
Timeout: time.Second * 5,
}
var GETTime time.Duration
err := task.Run(o.ctx, func() error {
startTime := time.Now()
probeURL := "https://www.google.com/generate_204"
if o.config.ProbeUrl != "" {
probeURL = o.config.ProbeUrl
}
req, _ := http.NewRequest(http.MethodGet, probeURL, nil)
utils.TryDefaultHeadersWith(req.Header, "nav")
response, err := httpClient.Do(req)
if err != nil {
return errors.New("outbound failed to relay connection").Base(err)
}
if response.Body != nil {
response.Body.Close()
}
endTime := time.Now()
GETTime = endTime.Sub(startTime)
return nil
})
if err != nil {
errorMessage := "the outbound " + outbound + " is dead: GET request failed:" + err.Error() + "with outbound handler report underlying connection failed"
errors.LogInfoInner(o.ctx, errorCollectorForRequest.UnderlyingError(), errorMessage)
return ProbeResult{Alive: false, LastErrorReason: errorMessage}
}
errors.LogInfo(o.ctx, "the outbound ", outbound, " is alive:", GETTime.Seconds())
return ProbeResult{Alive: true, Delay: GETTime.Milliseconds()}
}
func (o *Observer) updateStatusForResult(outbound string, result *ProbeResult) {View on GitHub (pinned to 7d214f8b09)
Solutions
- Set observatory config ProbeUrl to an endpoint reachable through your outbounds (e.g. https://www.gstatic.com/generate_204 or a self-hosted 204 URL).
- If the outbound is legitimately slow, accept occasional dead marks or raise health-check tolerance via burst observatory with tuned interval.
- Verify the outbound credentials/streamSettings if probes consistently fail while manual traffic also fails.
- Read the chained Base error and errorCollector.UnderlyingError() to distinguish dial vs TLS vs timeout.
Example fix
// before
"observatory": {"subjectSelector": ["proxy"]}
// after
"observatory": {
"subjectSelector": ["proxy"],
"probeURL": "https://www.gstatic.com/generate_204",
"probeInterval": "1m"
} Defensive patterns
Strategy: retry
Validate before calling
// pre-flight: confirm the probe URL is reachable through the outbound before scheduling probes
resp, err := probeClient.Get(probeURL)
if err != nil { chooseDifferentProbeURL() } Try / catch
// observatory already retries per probeInterval; consumers should tolerate transient dead marks
if !result.Alive && result.AlivePrev { /* flap: delay removal by one more interval */ } Prevention
- Point ProbeUrl at a highly available 204 endpoint.
- Do not treat a single failed probe as permanent node death; require consecutive failures.
- Keep probe timeouts (5s hardcoded) in mind for slow nodes.
When it happens
Trigger: httpClient.Do fails for any reason: the dialer path failed (see 45-47), TLS handshake exceeded 5 seconds, the 5-second overall client Timeout was exceeded, or the connection was reset mid-request. The observer then records ProbeResult{Alive:false} with the composed 'outbound X is dead' message.
Common situations: Classic in geo-restricted or censored networks where google.com is unreachable directly through the outbound; also common with slow outbounds that cannot complete a full TLS+HTTP round trip in 5 seconds, causing the observer to flap an actually-working node.
Related errors
- underlying connection error
- failed to produce report
- cannot understand address
- cannot dial remote address
- cannot finish connection
AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15).
Data as JSON: /api/errors/dc33aba207bffcb0.
Report an issue: GitHub.