grpc/grpc-go · error
failed to do connect handshake, response
Error message
failed to do connect handshake, response: %q
What it means
Fires in doHTTPConnectHandshake (proxy.go:89) when the proxy returns a non-200 status to CONNECT and the response was successfully dumped for diagnostics. gRPC expects HTTP/1.1 200 OK after a CONNECT; any other status means the proxy refused to establish the tunnel, and the full dumped response (status line, headers, body) is included so the reason is visible.
Solutions
- Read the dumped response: a 407 means add Proxy-Authorization (set user:pass in the proxy URL); 403 means ask the proxy admin to allowlist the host; 502/503/504 indicate proxy-to-upstream problems.
- Provide credentials via the proxy URL: https://user:pass@proxy:3128, or set Proxy-Authorization through grpc.WithContextDialer.
- Verify the target address and port are correct and reachable from the proxy's network.
- If you don't need the proxy, unset HTTPS_PROXY/HTTP_PROXY for the target host.
Example fix
// before
// HTTPS_PROXY=http://proxy.corp:3128 // no creds -> 407 Proxy Authentication Required
// after
os.Setenv("HTTPS_PROXY", "http://alice:s3cr3t@proxy.corp:3128")
// or, for hosts that shouldn't use the proxy:
os.Setenv("NO_PROXY", "internal-svc.cluster.local") Defensive patterns
Strategy: retry
Validate before calling
// Build a proxy URL with credentials if the proxy requires auth.
func proxyWithCreds(host, user, pass string) string {
u := &url.URL{Scheme: "http", Host: host}
if user != "" {
u.User = url.UserPassword(user, pass)
}
return u.String()
} Try / catch
if err := dialViaProxy(...); err != nil {
s := err.Error()
if strings.Contains(s, "407") { /* add Proxy-Authorization */ }
if strings.Contains(s, "403") { /* request allowlist */ }
if strings.Contains(s, "502") || strings.Contains(s, "503") { /* backoff + retry */ }
} Prevention
- Include user:pass in the proxy URL for authenticated proxies.
- Use NO_PROXY for hosts that must bypass the proxy.
- Verify the target address/port are reachable from the proxy network.
When it happens
Trigger: The proxy replies to CONNECT with a status other than 200. Common: 407 Proxy Authentication Required (missing/bad credentials), 403 Forbidden (host not allowlisted), 502 Bad Gateway / 503 Service Unavailable (proxy couldn't reach upstream), 504 Gateway Timeout. The dumped %q shows headers like Proxy-Authenticate and any error body.
Common situations: Corporate/egress proxy requiring credentials not supplied; proxy ACL blocking the target host/port; proxy can't reach the upstream (network partition); proxy rate-limiting or overloaded; mismatch between the proxy's expectations and the CONNECT target.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- failed to do connect handshake, status code
- reading server HTTP response
- failed to write the HTTP request
- ErrCodeEnhanceYourCalm
- gRPC requires a ResponseWriter supporting http.Flusher
AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11).
Data as JSON: /api/errors/e7ad7d814faf663b.
Report an issue: GitHub.
Appendix: source
Thrown at internal/transport/proxy.go:89
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
}
// proxyDial establishes a TCP connection to the specified address and performs an HTTP CONNECT handshake.
func proxyDial(ctx context.Context, addr resolver.Address, grpcUA string, opts proxyattributes.Options) (net.Conn, error) {
conn, err := internal.NetDialerWithTCPKeepalive().DialContext(ctx, "tcp", addr.Addr)
if err != nil {
return nil, err
}
return doHTTPConnectHandshake(ctx, conn, grpcUA, opts)View on GitHub (pinned to 0c51461d27)