grpc/grpc-go · error

failed to write the HTTP request

Error message

failed to write the HTTP request: %v

What it means

Fires in doHTTPConnectHandshake (proxy.go:75) when sendHTTPRequest fails to write the HTTP CONNECT request to the proxy connection. gRPC builds a CONNECT request (with optional Proxy-Authorization) and writes it to the TCP connection established to the proxy; if that write fails the handshake aborts and the connection is closed.

Solutions

  1. Verify the proxy address from HTTPS_PROXY/HTTP_PROXY (or grpc.WithContextDialer) resolves and accepts TCP connections.
  2. Check the proxy's logs/rules: it may be rejecting CONNECT to the target host or requiring authentication you didn't supply.
  3. Ensure the dial context has a generous-enough deadline; a too-short timeout can abort the write.
  4. If the proxy requires a TLS connection first, use an https:// proxy URL so Go dials TLS to the proxy before CONNECT.

Example fix

// before: proxy env points at a proxy that resets CONNECT
//   HTTPS_PROXY=http://broken-proxy:3128

// after: correct, reachable proxy with auth
import (
    _ "google.golang.org/grpc/credentials/insecure"
)
os.Setenv("HTTPS_PROXY", "http://user:pass@real-proxy:3128")
conn, err := grpc.NewClient(target, grpc.WithTransportCredentials(creds))
Defensive patterns

Strategy: try-catch

Validate before calling

// Verify proxy reachability before dialing gRPC.
func proxyReachable(proxyURL string) error {
    u, err := url.Parse(proxyURL)
    if err != nil {
        return err
    }
    c, err := net.DialTimeout("tcp", u.Host, 2*time.Second)
    if err != nil {
        return err
    }
    c.Close()
    return nil
}

Try / catch

conn, err := grpc.NewClient(target, opts...)
// proxy write failures surface during the first RPC or Dial.
// Wrap with a retry on transient network errors.
if err != nil {
    if isTransient(err) {
        // back off and retry NewClient / the RPC
    }
}

Prevention

When it happens

Trigger: Setting HTTPS_PROXY/HTTP_PROXY (or WithContextDialer proxy config) and the write to the proxy socket fails. Causes: the proxy closed/reset the connection immediately after TCP connect, a network drop mid-write, a broken pipe because the proxy rejected the client, or the context was cancelled before/during the write.

Common situations: Misconfigured or unreachable HTTP proxy; proxy that immediately drops CONNECT to non-allowlisted hosts; transient network instability between client and proxy; cancelled/timed-out dial context; proxy requiring TLS but reached as plain TCP.

Related errors


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

Appendix: source

Thrown at internal/transport/proxy.go:75

func doHTTPConnectHandshake(ctx context.Context, conn net.Conn, grpcUA string, opts proxyattributes.Options) (_ net.Conn, err error) {
	defer func() {
		if err != nil {
			conn.Close()
		}
	}()

	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

View on GitHub (pinned to 0c51461d27)