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

  1. Use xds.NewServerCredentials for servers and xds.NewClientCredentials for clients.
  2. Verify the credentials passed to grpc.NewServer were produced by NewServerCredentials.
  3. 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

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

Related errors


AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07). Data as JSON: /api/errors/68f8cfdbd5bc1aa0. Report an issue: GitHub.