grpc/grpc-go · error
xds: no peer certificates presented
Error message
xds: no peer certificates presented
What it means
Raised inside the custom peer-cert verifier (buildVerifyFunc) when the rawCerts slice passed by the TLS stack is empty — the peer presented no certificate at all. With InsecureSkipVerify set and a custom verifier expecting a peer cert, this is fatal.
Solutions
- Require the peer to present a certificate: server-side use tls.RequireAnyClientCert (or RequireAndVerifyClientCert) so the handshake fails early if absent.
- Ensure the client is configured with an identity provider when mTLS is required.
- Remove any TLS-terminating proxy between the peers, or configure it to forward client certificates.
- If using SPIFFE bundle verification on the server, confirm ClientAuth is dropped to RequireAnyClientCert as the code does in that branch.
Example fix
// before: cfg.ClientAuth = tls.NoClientCert // peer sends nothing, verifier errors // after: cfg.ClientAuth = tls.RequireAnyClientCert
Defensive patterns
Strategy: validation
Validate before calling
func requirePeerCerts(cfg *tls.Config) {
// for a server expecting mTLS/SPIFFE verification
cfg.ClientAuth = tls.RequireAnyClientCert // or RequireAndVerifyClientCert
} Try / catch
In VerifyPeerCertificate, if len(rawCerts)==0 return a clear 'peer did not present a certificate' error and close the connection; log it as a policy violation. Do not fall back to unverified.
Prevention
- Set ClientAuth >= RequireAnyClientCert whenever peer certs are expected.
- Do not put a TLS-terminating proxy in front of an mTLS-verifying server unless it forwards client certs.
- Test the handshake with a cert-less client to confirm it fails closed.
When it happens
Trigger: Server-side: requireClientCert was effectively lowered such that a client connected without a cert and the verifier still ran; client-side: the server sent no certificate chain during the handshake; a TLS terminator upstream stripped the cert.
Common situations: ClientAuth misconfigured (e.g. tls.RequireAnyClientCert not set when SPIFFE bundle verification is active, or a plain TLS client hitting an mTLS-expecting path); a load balancer terminating TLS and forwarding plain; network middlebox stripping client certs.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- xds: fetching identity certificates from…
- xds: received DNS SANs
- xds: received SANs do not match any of the accepted SANs
- overriding server name is not supported by xDS client TLS…
- spiffe: could not get spiffe ID from peer leaf cert but…
AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11).
Data as JSON: /api/errors/d0e88f88e78e519f.
Report an issue: GitHub.
Appendix: source
Thrown at internal/credentials/xds/handshake_info.go:256
if hi.identityProvider != nil {
km, err := hi.identityProvider.KeyMaterial(ctx)
if err != nil {
return nil, fmt.Errorf("xds: fetching identity certificates from CertificateProvider failed: %v", err)
}
cfg.Certificates = km.Certs
}
if envconfig.XDSSNIEnabled && sni != "" {
cfg.ServerName = sni
}
return cfg, nil
}
func (hi *HandshakeInfo) buildVerifyFunc(km *certprovider.KeyMaterial, isClient bool, sni string) func(rawCerts [][]byte, _ [][]*x509.Certificate) error {
return func(rawCerts [][]byte, _ [][]*x509.Certificate) error {
if len(rawCerts) == 0 {
return fmt.Errorf("xds: no peer certificates presented")
}
// Parse all raw certificates presented by the peer.
var certs []*x509.Certificate
for _, rc := range rawCerts {
cert, err := x509.ParseCertificate(rc)
if err != nil {
return err
}
certs = append(certs, cert)
}
// Build the intermediates list and verify that the leaf certificate is
// signed by one of the root certificates. If a SPIFFE Bundle Map is
// configured, it is used to get the root certs. Otherwise, the
// configured roots in the root provider are used.
intermediates := x509.NewCertPool()
for _, cert := range certs[1:] {
intermediates.AddCert(cert)View on GitHub (pinned to 0c51461d27)