grpc/grpc-go · error

xds: required field `xds_servers` not found in bootstrap con

Error message

xds: required field `xds_servers` not found in bootstrap configuration: %s

What it means

After the bootstrap JSON is successfully parsed the library checks that the xds_servers slice is non-empty (bootstrap.go:631). xds_servers is the only structurally required field because it tells the client where to connect for xDS resources. If the field is absent or set to an empty array, the bootstrap is considered invalid.

Source

Thrown at internal/xds/bootstrap/bootstrap.go:631

		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))
		if authority.ClientListenerResourceNameTemplate == "" {
			authority.ClientListenerResourceNameTemplate = prefix + "/envoy.config.listener.v3.Listener/%s"
			continue
		}
		if !strings.HasPrefix(authority.ClientListenerResourceNameTemplate, prefix) {
			return fmt.Errorf("xds: field clientListenerResourceNameTemplate %q of authority %q doesn't start with prefix %q", authority.ClientListenerResourceNameTemplate, name, prefix)
		}
	}
	return nil
}

View on GitHub (pinned to 03255a9237)

Solutions

  1. Add an xds_servers array with at least one server entry containing server_uri, channel_creds, and server_features.
  2. Double-check the key spelling: it must be exactly xds_servers (snake_case, plural).
  3. If using GRPC_XDS_BOOTSTRAP_CONFIG, ensure the inline JSON includes the full xds_servers block.
  4. Regenerate the bootstrap with the intended management server endpoint.

Example fix

// before:
//   { "node": { "id": "my-client" } }
// after:
//   {
//     "xds_servers": [{
//       "server_uri": "dns:///trafficdirector.googleapis.com:443",
//       "channel_creds": [{ "type": "google_default" }],
//       "server_features": ["xds_v3"]
//     }],
//     "node": { "id": "my-client" }
//   }
Defensive patterns

Strategy: validation

Validate before calling

// Ensure xds_servers is present and non-empty before loading.
func hasXDSServers(data []byte) error {
    var probe struct{ XDSServers []json.RawMessage `json:"xds_servers"` }
    if err := json.Unmarshal(data, &probe); err != nil {
        return err
    }
    if len(probe.XDSServers) == 0 {
        return errors.New("bootstrap missing required non-empty xds_servers")
    }
    return nil
}

Prevention

When it happens

Trigger: The JSON is valid but the top-level xds_servers key is missing entirely, is misspelled (e.g. xds_server), or is present as an empty array. Also happens when a templating step strips the servers section for a non-production environment.

Common situations: Using a minimal/dev bootstrap that omits xds_servers by mistake; an overlay merged two configs and clobbered the servers; the envoy/bootstrap generator was configured without a management server URI.

Related errors


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