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 when the credentials were created for client-side use (NewClientCredentials) but ServerHandshake is called on them. This is the mirror of the client-handshake guard: xDS credentials are directional and the isClient flag is true, so the guard at line 160-161 fires.

Solutions

  1. Use xds.NewServerCredentials for grpc.NewServer and xds.NewClientCredentials for grpc.Dial.
  2. Separate client and server credential construction into distinct functions or variables to prevent confusion.

Example fix

// before
clientCreds, _ := xds.NewClientCredentials(opts)
srv := grpc.NewServer(grpc.Creds(clientCreds)) // error
// after
serverCreds, _ := xds.NewServerCredentials(serverOpts)
srv := grpc.NewServer(grpc.Creds(serverCreds))
Defensive patterns

Strategy: validation

Validate before calling

// Ensure server-side xDS credentials are used for NewServer:
serverCreds, err := xds.NewServerCredentials(serverOpts)
if err != nil { return err }
srv := grpc.NewServer(grpc.Creds(serverCreds))

Type guard

// No public field to check; prevent misuse by isolating server credential construction.

Prevention

When it happens

Trigger: A credentials instance created via xds.NewClientCredentials is passed to grpc.NewServer (which calls ServerHandshake), or is otherwise used in a server context.

Common situations: Developers pass client credentials to a gRPC server, often by sharing or mislabeling a credential variable. Also occurs in bidirectional test setups that reuse one credential object.

Understand the failure class

Related errors


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

Appendix: 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 0c51461d27)