grpc/grpc-go · error
xds: CertificateProvider to fetch trusted roots is missing…
Error message
xds: CertificateProvider to fetch trusted roots is missing, cannot perform TLS handshake. Please check configuration on the management server
What it means
Returned by clientSideTLSConfigInternal when hi.rootProvider is nil. On the client side, the root certificate provider is mandatory—it supplies the trusted CA certificates needed to validate the server's certificate chain. Without it, the TLS handshake cannot verify the peer, so gRPC refuses to proceed. The error message directs you to the management server because root provider configuration originates from the xDS DownstreamTLSContext's validation context.
Solutions
- On the xDS management server, add a validation context (CertificateValidationContext with trusted_ca) to the DownstreamTLSContext for the client cluster.
- Verify the xDS LDS resource includes both CommonTlsContext.certificates (identity) and CommonTlsContext.validation_context (trusted roots).
- Use grpc.WithTransportCredentials with fallback credentials if xDS TLS is not strictly required, so the connection falls back when xDS config is incomplete.
- Check xDS config dump (e.g., istioctl proxy-config cluster) to confirm the security policy has a validation context.
Example fix
// On the xDS management server (e.g., Istio DestinationRule): // before: missing validation context // after: tls: mode: MUTUAL clientCertificate: /etc/certs/client.pem privateKey: /etc/certs/client.key caCertificates: /etc/certs/ca-cert.pem # <-- this provides the root provider
Defensive patterns
Strategy: validation
Validate before calling
// Validate xDS security config has a validation context before relying on xDS TLS. // This is config-side validation on the management server. // Ensure DownstreamTLSContext has a CertificateValidationContext with trusted_ca. // Use istioctl proxy-config to dump and inspect the LDS resource: // istioctl proxy-config listener <pod> -o json | jq '...validation_context...'
Try / catch
// Check the RPC error and log whether root provider is the issue.
err := client.Call(ctx, req)
if err != nil && strings.Contains(err.Error(), "fetch trusted roots is missing") {
log.Error("xDS security config is missing root cert provider; check management server")
} Prevention
- Always configure a validation context (trusted CA) alongside identity certs in xDS TLS.
- Validate xDS security config completeness in CI/CD before deploying.
- Use xDS config linting tools (istioctl analyze) to catch missing fields.
- Monitor SDS/cert provider health for root cert delivery.
When it happens
Trigger: Client-side xDS TLS handshake where the HandshakeInfo exists and is not fallback (UseFallbackCreds() is false) but rootProvider was never set—i.e., NewHandshakeInfo was called with a nil rootProvider. This happens when the xDS security config has an identity provider but no validation/trusted-roots context.
Common situations: xDS DownstreamTLSContext is configured with a certificate provider for client identity (mTLS) but omits the validation context (trusted CA); xDS management server sends a partially-populated security policy; misconfigured Istio/Envoy PeerAuthentication or DestinationRule that sets client cert but not CA.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- xds: CertificateProvider to fetch identity certificate is…
- xds: connection closed or HandshakeInfo dead
- failed to build credentials bundle from bootstrap for
- overriding server name is not supported by xDS client TLS…
- server handshake is not supported by xDS client TLS…
AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11).
Data as JSON: /api/errors/dd9e5ad818f5f0e4.
Report an issue: GitHub.
Appendix: source
Thrown at internal/credentials/xds/handshake_info.go:209
// GetSANMatchersForTesting returns the SAN matchers stored in HandshakeInfo.
// To be used only for testing purposes.
func (hi *HandshakeInfo) GetSANMatchersForTesting() []matcher.StringMatcher {
return append([]matcher.StringMatcher{}, hi.sanMatchers...)
}
// clientSideTLSConfigInternal constructs a tls.Config to be used in a
// client-side handshake based on the contents of the HandshakeInfo.
//
// hostname is passed as a parameter here instead of being part of the
// HandshakeInfo because HandshakeInfo contains cluster-level security
// configuration that applies to all endpoints in the cluster, while hostname is
// specific to each endpoint. This allows sharing a single HandshakeInfo
// instance across multiple endpoints in the same cluster.
func (hi *HandshakeInfo) clientSideTLSConfigInternal(ctx context.Context, hostname string) (*tls.Config, error) {
// On the client side, rootProvider is mandatory. IdentityProvider is
// optional based on whether the client is doing TLS or mTLS.
if hi.rootProvider == nil {
return nil, errors.New("xds: CertificateProvider to fetch trusted roots is missing, cannot perform TLS handshake. Please check configuration on the management server")
}
// InsecureSkipVerify needs to be set to true because we need to perform
// custom verification to check the SAN on the received certificate.
// Currently the Go stdlib does complete verification of the cert (which
// includes hostname verification) or none. We are forced to go with the
// latter and perform the normal cert validation ourselves.
cfg := &tls.Config{
InsecureSkipVerify: true,
NextProtos: []string{"h2"},
}
km, err := hi.rootProvider.KeyMaterial(ctx)
if err != nil {
return nil, fmt.Errorf("xds: fetching trusted roots from CertificateProvider failed: %v", err)
}
cfg.RootCAs = km.Roots
View on GitHub (pinned to 0c51461d27)