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

  1. Use xds.NewClientCredentials for client-side (grpc.Dial) and xds.NewServerCredentials for server-side (grpc.NewServer).
  2. 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

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

Related errors


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)