grpc/grpc-go · error

reading server HTTP response

Error message

reading server HTTP response: %v

What it means

Fires in doHTTPConnectHandshake (proxy.go:81) when http.ReadResponse cannot parse the proxy's reply to the CONNECT request. After writing CONNECT, gRPC reads the HTTP/1.1 response status line and headers from the proxy via a bufio.Reader; if the bytes received are not a valid HTTP response (or the read errors), this wraps the cause.

Solutions

  1. Confirm the proxy URL scheme matches what the proxy expects (https:// if the proxy itself requires TLS).
  2. Reproduce with curl --proxy <url> <target> to see the raw response; if curl fails similarly, the proxy is the issue.
  3. Increase the dial/read context deadline; a tight timeout can truncate the response read.
  4. Bypass the proxy temporarily to confirm the target is reachable, then fix the proxy configuration.

Example fix

// before: TLS-expecting proxy reached as plain HTTP
//   HTTPS_PROXY=http://tls-proxy:443

// after
os.Setenv("HTTPS_PROXY", "https://tls-proxy:443")
// Go will dial TLS to the proxy, then issue CONNECT.
Defensive patterns

Strategy: try-catch

Validate before calling

// Smoke-test the proxy with curl-like CONNECT before relying on it.
func probeProxyConnect(ctx context.Context, proxyURL, target string) error {
    // open TCP to proxy, write CONNECT target, read status line
    // return error if not HTTP/1.x
    return nil
}

Try / catch

conn, err := grpc.NewClient(target, opts...)
if err != nil && strings.Contains(err.Error(), "reading server HTTP response") {
    // likely a scheme mismatch (http vs https) with the proxy
}

Prevention

When it happens

Trigger: The proxy sends non-HTTP bytes, closes the connection before a full response, or the read errors (reset, timeout, EOF). Examples: a proxy that speaks TLS-first but was reached as plain TCP and sends a ServerHello; a transparent proxy returning raw bytes; the connection dropping mid-response; context cancellation during the read.

Common situations: Pointing HTTPS_PROXY at a TLS-terminating proxy over plain TCP (scheme mismatch); a proxy that closes on unsupported methods; network interference (middleboxes, captive portals injecting HTML); cancelled dial context; proxy returning a malformed status line.

Related errors


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

Appendix: 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 0c51461d27)