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

  1. Retry the update — transient stalls on geodata mirrors usually succeed on a second attempt
  2. Point config.Assets / the asset URL at a faster, closer mirror
  3. Route the geodata downloader through a healthy outbound (set the geodata outbound tag to a working proxy)
  4. 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

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

Related errors


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