grpc/grpc-go · error
xds: config parsing for certificate provider plugin
Error message
xds: config parsing for certificate provider plugin %q failed during bootstrap: %v
What it means
Returned by Config.UnmarshalJSON when a certificate provider plugin's ParseConfig fails. Each certificate_providers entry names a plugin and a config blob; if the plugin rejects the config, bootstrap fails with the instance name printed.
Solutions
- Read the nested %v to see which constraint the plugin rejected.
- For file_watcher, supply the required fields: at least one of certificate_file or ca_certificate_file, plus private_key_file when a cert is given, and a refresh_interval.
- Verify the plugin_name is spelled exactly (commonly 'file_watcher').
- Confirm the cert provider plugin is built into your grpc-go (pemfile is).
Example fix
// before (missing required file fields)
"certificate_providers":{"default":{"plugin_name":"file_watcher","config":{"refresh_interval":"1s"}}}
// after
"certificate_providers":{"default":{"plugin_name":"file_watcher","config":{"certificate_file":"/etc/certs/client.crt","private_key_file":"/etc/certs/client.key","ca_certificate_file":"/etc/certs/ca.crt","refresh_interval":"1s"}}} Defensive patterns
Strategy: validation
Validate before calling
// Validate a file_watcher certificate provider config block.
func validateFileWatcher(cfg map[string]any) error {
refresh, _ := cfg["refresh_interval"].(string)
if refresh == "" {
return fmt.Errorf("file_watcher: refresh_interval required")
}
cert, _ := cfg["certificate_file"].(string)
ca, _ := cfg["ca_certificate_file"].(string)
if cert == "" && ca == "" {
return fmt.Errorf("file_watcher: need certificate_file or ca_certificate_file")
}
return nil
} Try / catch
if _, err := bootstrap.NewConfigFromContents(data); err != nil {
if strings.Contains(err.Error(), "certificate provider plugin") {
// read nested cause, fix the named provider's config.
}
} Prevention
- For file_watcher, always set refresh_interval and at least one cert file.
- Keep plugin_name spelling exact (file_watcher).
- Test the bootstrap in a staging control plane before production.
When it happens
Trigger: Triggered at bootstrap.go:620 when parser.ParseConfig(nameAndConfig.Config) errors. Most commonly the file_watcher (pemfile) plugin rejects a config missing required file fields or with invalid values.
Common situations: file_watcher config missing certificate_file/private_key_file when mTLS is required; bad refresh interval; unknown plugin-specific field; mismatched plugin_name spelling.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- failed to build credentials bundle from bootstrap for
- missing server_listener_resource_name_template in the…
- xds: `channel_creds` field in server config cannot be empty
- xds: error normalizing JSON bootstrap configuration
- xds: failed to JSON unmarshal server configurations during…
AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11).
Data as JSON: /api/errors/dd6061c872a588ee.
Report an issue: GitHub.
Appendix: source
Thrown at internal/xds/bootstrap/bootstrap.go:620
c.cpcs = config.CertificateProviders
c.serverListenerResourceNameTemplate = config.ServerListenerResourceNameTemplate
c.clientDefaultListenerResourceNameTemplate = config.ClientDefaultListenerResourceNameTemplate
c.authorities = config.Authorities
c.node = config.Node
// Build the certificate providers configuration to ensure that it is valid.
cpcCfgs := make(map[string]*certprovider.BuildableConfig)
getBuilder := internal.GetCertificateProviderBuilder.(func(string) certprovider.Builder)
for instance, nameAndConfig := range c.cpcs {
name := nameAndConfig.PluginName
parser := getBuilder(nameAndConfig.PluginName)
if parser == nil {
// We ignore plugins that we do not know about.
continue
}
bc, err := parser.ParseConfig(nameAndConfig.Config)
if err != nil {
return fmt.Errorf("xds: config parsing for certificate provider plugin %q failed during bootstrap: %v", name, err)
}
cpcCfgs[instance] = bc
}
c.certProviderConfigs = cpcCfgs
// Default value of the default client listener name template is "%s".
if c.clientDefaultListenerResourceNameTemplate == "" {
c.clientDefaultListenerResourceNameTemplate = "%s"
}
if len(c.xDSServers) == 0 {
return fmt.Errorf("xds: required field `xds_servers` not found in bootstrap configuration: %s", string(data))
}
// Post-process the authorities' client listener resource template field:
// - if set, it must start with "xdstp://<authority_name>/"
// - if not set, it defaults to "xdstp://<authority_name>/envoy.config.listener.v3.Listener/%s"
for name, authority := range c.authorities {
prefix := fmt.Sprintf("xdstp://%s", url.PathEscape(name))View on GitHub (pinned to 0c51461d27)