XTLS/Xray-core · error
connection idle timeout
Error message
connection idle timeout
What it means
Produced by the idleConn wrapper around sockets used by the geodata downloader. Each Read arms a time.AfterFunc(idleTimeout) that closes the connection; if the timer already fired before Read returned (t.Stop() == false), the peer sent no bytes for idleTimeout and the connection was force-closed, so the read is reported as an idle timeout instead of hanging forever.
Source
Thrown at app/geodata/download.go:49
type downloader struct {
ctx context.Context
httpClient *http.Client
httpsClient *http.Client
}
type idleConn struct {
net.Conn
}
func (c *idleConn) Read(b []byte) (int, error) {
t := time.AfterFunc(idleTimeout, func() {
_ = c.Close()
})
n, err := c.Conn.Read(b)
if !t.Stop() {
_ = c.Close()
return n, errors.New("connection idle timeout")
}
return n, err
}
func (c *idleConn) Write(b []byte) (int, error) {
return c.Conn.Write(b)
}
func newDownloader(ctx context.Context, dispatcher routing.Dispatcher, outbound string) *downloader {
return &downloader{
ctx: ctx,
httpClient: newClient(ctx, dispatcher, outbound, false),
httpsClient: newClient(ctx, dispatcher, outbound, true),
}
}
func newClient(baseCtx context.Context, dispatcher routing.Dispatcher, outbound string, isHTTPS bool) *http.Client {
dial := func(ctx context.Context, network, address string) (net.Conn, error) {View on GitHub (pinned to 7d214f8b09)
Solutions
- Retry the update — transient stalls on geodata mirrors usually succeed on a second attempt
- Point config.Assets / the asset URL at a faster, closer mirror
- Route the geodata downloader through a healthy outbound (set the geodata outbound tag to a working proxy)
- Pre-download geodata files manually into the working directory so the updater never needs the network
Defensive patterns
Strategy: retry
Validate before calling
// Before updating, probe the mirror with a short read deadline to detect stalls early:
conn, err := net.DialTimeout("tcp", mirrorHost, 5*time.Second)
if err != nil { /* skip mirror, try next asset URL */ }
conn.Close() Try / catch
for attempt := 0; attempt < 3; attempt++ {
err = downloader.Update()
if err == nil { break }
if strings.Contains(err.Error(), "connection idle timeout") {
time.Sleep(time.Duration(attempt+1) * time.Second) // backoff, then retry
continue
}
break // non-idle error, do not retry
} Prevention
- Ship geodata files with the deployment so updates are optional
- Prefer mirrors with good throughput to reduce stall windows
- Schedule geodata cron during low-traffic hours
When it happens
Trigger: Any geodata download (geosite.dat / geoip.dat update) where the remote server stalls — the response body stops arriving for longer than idleTimeout while io.Copy is still reading through idleConn.Read.
Common situations: Slow or rate-limited geodata mirrors (GitHub raw, jsdelivr) throttling mid-transfer; proxied outbound with an unstable upstream; very large geodata files over a lossy link where a read gap exceeds the timeout.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- cannot understand address
- cannot dial remote address
- cannot finish connection
- redirected to non-https URL:
- stopped after 10 redirects
AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15).
Data as JSON: /api/errors/4e7de877d9f77953.
Report an issue: GitHub.