grpc/grpc-go · error
ServerHandshake is not supported for client credentials
Error message
ServerHandshake is not supported for client credentials
What it means
Returned by credsImpl.ServerHandshake (credentials/xds/xds.go:161) when c.isClient is true. Symmetric to error 28: the credsImpl rejects calling the server-side handshake method on a client-credentials instance. The check is the very first statement of ServerHandshake so it fails before any TLS state is touched.
Source
Thrown at credentials/xds/xds.go:161
}
case <-ctx.Done():
conn.Close()
return nil, nil, ctx.Err()
}
info := credentials.TLSInfo{
State: conn.ConnectionState(),
CommonAuthInfo: credentials.CommonAuthInfo{
SecurityLevel: credentials.PrivacyAndIntegrity,
},
SPIFFEID: credinternal.SPIFFEIDFromState(conn.ConnectionState()),
}
return credinternal.WrapSyscallConn(rawConn, conn), info, nil
}
// ServerHandshake performs the TLS handshake on the server-side.
func (c *credsImpl) ServerHandshake(rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) {
if c.isClient {
return nil, nil, errors.New("ServerHandshake is not supported for client credentials")
}
// An xds-enabled gRPC server wraps the underlying raw net.Conn in a type
// that provides a way to retrieve `HandshakeInfo`, which contains the
// certificate providers to be used during the handshake. If the net.Conn
// passed to this function does not implement this interface, or if the
// `HandshakeInfo` does not contain the information we are looking for, we
// delegate the handshake to the fallback credentials.
hiConn, ok := rawConn.(interface {
XDSHandshakeInfo() (*grpcsync.RefCounted[xdsinternal.HandshakeInfo], error)
})
if !ok {
return c.fallback.ServerHandshake(rawConn)
}
hi, err := hiConn.XDSHandshakeInfo()
if err != nil {
return nil, nil, err
}View on GitHub (pinned to 03255a9237)
Solutions
- Use xds.NewServerCredentials for servers and xds.NewClientCredentials for clients.
- Verify the credentials passed to grpc.NewServer were produced by NewServerCredentials.
- Keep client and server credential setup in separate functions/variables.
Example fix
// before
creds, _ := xds.NewClientCredentials(xds.ClientOptions{FallbackCreds: insecure.NewCredentials()})
srv := grpc.NewServer(grpc.Creds(creds)) // ServerHandshake fails
// after
creds, _ := xds.NewServerCredentials(xds.ServerOptions{FallbackCreds: insecure.NewCredentials()})
srv := grpc.NewServer(grpc.Creds(creds)) Defensive patterns
Strategy: type-guard
Type guard
// Mirror of error 28: guard by construction so server creds are never
// handed to a client and vice-versa.
type serverXDS struct{ credentials.TransportCredentials } Try / catch
// Surfaces on the first inbound handshake; prevent by construction (see tips).
Prevention
- Keep server credential construction isolated from client credential construction.
- Use distinct variable names and code reviews focused on the New*Client*/New*Server* pairing.
When it happens
Trigger: Creating credentials with xds.NewClientCredentials(...) and passing them to grpc.NewServer(grpc.Creds(...)) so the server tries ServerHandshake on client creds. Surfaces on the first inbound connection handshake.
Common situations: Reusing a client-credentials variable in server setup; full-duplex service that mixed up the two New* calls; refactor that merged client and server credential setup.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- ClientHandshake() is not supported for server credentials
- serverName for peer validation must be configured as a list
- missing fallback credentials
- token file access error
- empty token_exchange_service_uri in options
AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07).
Data as JSON: /api/errors/68f8cfdbd5bc1aa0.
Report an issue: GitHub.