grpc/grpc-go · error
ClientHandshake() is not supported for server credentials
Error message
ClientHandshake() is not supported for server credentials
What it means
Returned by credsImpl.ClientHandshake when the credentials were created for server-side use (NewServerCredentials) but ClientHandshake is called on them. xDS credentials are directional: client credentials perform client-side TLS handshakes and server credentials perform server-side handshakes; calling the wrong method is a programming error.
Solutions
- Use xds.NewClientCredentials for client-side (grpc.Dial) and xds.NewServerCredentials for server-side (grpc.NewServer).
- Audit shared credential variables and split them into separate client and server instances.
Example fix
// before serverCreds, _ := xds.NewServerCredentials(opts) conn, _ := grpc.Dial(addr, grpc.WithTransportCredentials(serverCreds)) // error // after clientCreds, _ := xds.NewClientCredentials(clientOpts) conn, _ := grpc.Dial(addr, grpc.WithTransportCredentials(clientCreds))
Defensive patterns
Strategy: validation
Validate before calling
// Ensure client-side xDS credentials are used for Dial:
clientCreds, err := xds.NewClientCredentials(clientOpts)
if err != nil { return err }
// Verify it is NOT a server credential by keeping client/server construction separate. Type guard
// xDS credentials do not expose a public isClient flag. // Prevent misuse by keeping client and server credential construction in separate, clearly named functions.
Prevention
- Never share a credential instance between Dial and NewServer.
- Name variables clearly: clientXCreds vs serverXCreds.
- Review test harnesses that reuse credential objects.
When it happens
Trigger: A credentials instance created via xds.NewServerCredentials is passed to grpc.Dial (which calls ClientHandshake), or is otherwise used in a client context. The isClient flag is false, so the guard at line 95-96 fires.
Common situations: Developers confuse client and server xDS credential constructors, or share a single credentials variable between dial and server code. Also seen in test harnesses that reuse a credential instance for both directions.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- ServerHandshake is not supported for client credentials
- failed to build call credentials from bootstrap for
- failed to build credentials bundle from bootstrap for
- missing fallback credentials
- server handshake is not supported by xDS client TLS…
AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11).
Data as JSON: /api/errors/0a95b4133d97ce65.
Report an issue: GitHub.
Appendix: source
Thrown at credentials/xds/xds.go:96
// credsImpl is an implementation of the credentials.TransportCredentials
// interface which uses xDS APIs to fetch its security configuration.
type credsImpl struct {
isClient bool
fallback credentials.TransportCredentials
}
// ClientHandshake performs the TLS handshake on the client-side.
//
// It looks for the presence of a HandshakeInfo value in the passed in context
// (added using a call to NewContextWithHandshakeInfo()), and retrieves identity
// and root certificates from there. It also retrieves a list of acceptable SANs
// and uses a custom verification function to validate the certificate presented
// by the peer. It uses fallback credentials if no HandshakeInfo is present in
// the passed in context.
func (c *credsImpl) ClientHandshake(ctx context.Context, authority string, rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) {
if !c.isClient {
return nil, nil, errors.New("ClientHandshake() is not supported for server credentials")
}
// The clusterimpl balancer constructs a new HandshakeInfo using a call to
// NewHandshakeInfo(), and then adds it to the attributes field of the
// resolver.Address when handling calls to NewSubConn(). The transport layer
// takes care of shipping these attributes in the context to this handshake
// function. We first read the credentials.ClientHandshakeInfo type from the
// context, which contains the attributes added by the clusterimpl balancer.
// We then read the HandshakeInfo from the attributes to get to the actual
// data that we need here for the handshake.
chi := credentials.ClientHandshakeInfoFromContext(ctx)
// If there are no attributes in the received context or the attributes does
// not contain a HandshakeInfo, it could either mean that the user did not
// specify an `xds` scheme in their dial target or that the xDS server did
// not provide any security configuration. In both of these cases, we use
// the fallback credentials specified by the user.
if chi.Attributes == nil {
return c.fallback.ClientHandshake(ctx, authority, rawConn)View on GitHub (pinned to 0c51461d27)