grpc/grpc-go · error

reading server HTTP response: %v

Error message

reading server HTTP response: %v

What it means

This error occurs in doHTTPConnectHandshake when http.ReadResponse fails to read the proxy's response to the CONNECT request. The bufio.Reader could not parse a valid HTTP response from the proxy — the connection may have closed, timed out, or the proxy sent a non-HTTP response.

Source

Thrown at internal/transport/proxy.go:81

	req := &http.Request{
		Method: http.MethodConnect,
		URL:    &url.URL{Host: opts.ConnectAddr},
		Header: map[string][]string{"User-Agent": {grpcUA}},
	}
	if user := opts.User; user != nil {
		u := user.Username()
		p, _ := user.Password()
		req.Header.Add(proxyAuthHeaderKey, "Basic "+basicAuth(u, p))
	}
	if err := sendHTTPRequest(ctx, req, conn); err != nil {
		return nil, fmt.Errorf("failed to write the HTTP request: %v", err)
	}

	r := bufio.NewReader(conn)
	resp, err := http.ReadResponse(r, req)
	if err != nil {
		return nil, fmt.Errorf("reading server HTTP response: %v", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		dump, err := httputil.DumpResponse(resp, true)
		if err != nil {
			return nil, fmt.Errorf("failed to do connect handshake, status code: %s", resp.Status)
		}
		return nil, fmt.Errorf("failed to do connect handshake, response: %q", dump)
	}
	// The buffer could contain extra bytes from the target server, so we can't
	// discard it. However, in many cases where the server waits for the client
	// to send the first message (e.g. when TLS is being used), the buffer will
	// be empty, so we can avoid the overhead of reading through this buffer.
	if r.Buffered() != 0 {
		return &bufConn{Conn: conn, r: r}, nil
	}
	return conn, nil
}

View on GitHub (pinned to 03255a9237)

Solutions

  1. Confirm the proxy address is an HTTP/HTTPS proxy that supports CONNECT tunneling (not a SOCKS or raw TCP service).
  2. Check proxy logs for connection drops or errors on the target port.
  3. Test the proxy manually: curl -x http://proxy:port https://target to isolate gRPC vs proxy issues.
  4. Retry with backoff if the proxy is intermittently unreliable.

Example fix

// before (broken): pointing at a non-HTTP proxy port
os.Setenv("HTTPS_PROXY", "http://10.0.0.1:9999") // raw TCP service

// after (valid): correct HTTP proxy address
os.Setenv("HTTPS_PROXY", "http://10.0.0.1:3128") // squid/tinyproxy HTTP CONNECT
Defensive patterns

Strategy: retry

Validate before calling

// Verify the proxy supports HTTP CONNECT before using it
func testProxyConnect(proxyURL, targetHost string) error {
    u, err := url.Parse(proxyURL)
    if err != nil { return err }
    conn, err := net.DialTimeout("tcp", u.Host, 5*time.Second)
    if err != nil { return err }
    defer conn.Close()
    req := fmt.Sprintf("CONNECT %s HTTP/1.1\r\nHost: %s\r\n\r\n", targetHost, targetHost)
    _, err = conn.Write([]byte(req))
    return err
}

Try / catch

conn, err := grpc.Dial(target,
    grpc.WithConnectParams(grpc.ConnectParams{
        Backoff: backoff.Config{BaseDelay: 2*time.Second, MaxDelay: 30*time.Second},
        MinConnectTimeout: 10 * time.Second,
    }),
)
if err != nil {
    // check if it's a proxy response read failure
    if strings.Contains(err.Error(), "reading server HTTP response") {
        log.Printf("proxy may not support CONNECT or is unhealthy")
    }
}

Prevention

When it happens

Trigger: After successfully writing the CONNECT request to the proxy, reading the response fails. This happens when the proxy closes the connection abruptly, sends garbage/non-HTTP data, or the connection times out before a full response is received.

Common situations: Proxy is overloaded and drops connections mid-response, proxy is not actually an HTTP proxy (e.g., pointing at a plain TCP service), TLS-intercepting proxy that resets plain CONNECT, or the proxy returns a response larger than the read buffer under network congestion.

Related errors


AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07). Data as JSON: /api/errors/20671c55a2943e52. Report an issue: GitHub.