grpc/grpc-go · critical

missing server_listener_resource_name_template in the…

Error message

missing server_listener_resource_name_template in the bootstrap configuration

What it means

Returned by xds.NewGRPCServer (xds/server.go:111) when the xDS client's bootstrap configuration has an empty server_listener_resource_name_template. On the server side this template is mandatory because gRPC uses it to derive the Listener resource name it subscribes to (e.g. 'grpc/server?<ip:port>'); without it the server cannot know which xDS Listener resource to watch, so construction fails fast. NewGRPCServer also closes the freshly created xDS client before returning the error.

Solutions

  1. Add a non-empty server_listener_resource_name_template to the bootstrap JSON, e.g. "grpc/server?%s" (the %s is replaced with the server's IP:port).
  2. Point GRPC_XDS_BOOTSTRAP at a bootstrap file that includes the xds_servers entry AND server_listener_resource_name_template.
  3. Regenerate the bootstrap from your control plane / bootstrap generator with server-side fields enabled.
  4. Verify after construction by reading xdsClient.BootstrapConfig().ServerListenerResourceNameTemplate() before calling Serve.

Example fix

// before (bootstrap.json)
{
  "xds_servers": [{ "server_config": {...} }]
}
// -> NewGRPCServer fails: missing server_listener_resource_name_template

// after
{
  "xds_servers": [{ "server_config": {...} }],
  "server_listener_resource_name_template": "grpc/server?%s"
}
Defensive patterns

Strategy: validation

Validate before calling

// Validate the bootstrap has the server template before constructing the xDS server.
import "google.golang.org/grpc/xds"

if os.Getenv("GRPC_XDS_BOOTSTRAP") == "" && os.Getenv("GRPC_XDS_BOOTSTRAP_FILE_CONTENT") == "" {
    log.Fatal("xDS bootstrap not configured")
}
// Optionally parse the bootstrap JSON and assert server_listener_resource_name_template != "".

Try / catch

gs, err := xds.NewGRPCServer(opts...)
if err != nil {
    if strings.Contains(err.Error(), "server_listener_resource_name_template") {
        // fix bootstrap JSON and retry; do not proceed to Serve
    }
}

Prevention

When it happens

Trigger: Calling xds.NewGRPCServer(opts...) where GRPC_XDS_BOOTSTRAP (or the bootstrap file at the default path) lacks the server_listener_resource_name_template field, or it is present but empty. Also triggered when the env var points at a bootstrap JSON intended only for clients.

Common situations: Reusing a client-only bootstrap file for an xDS server; bootstrap generated by a control plane that omits the server template; typo in the field name; bootstrap env var unset so a client-style default is synthesized.

Related errors


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

Appendix: source

Thrown at xds/server.go:111

	// Initializing the xDS client upfront (instead of at serving time)
	// simplifies the code by eliminating the need for a mutex to protect the
	// xdsC and xdsClientClose fields.
	pool := xdsClientPool
	if s.opts.clientPoolForTesting != nil {
		pool = s.opts.clientPoolForTesting
	}
	xdsClient, xdsClientClose, err := pool.NewClient(xdsclient.NameForServer, mrl)
	if err != nil {
		return nil, fmt.Errorf("xDS client creation failed: %v", err)
	}

	// Validate the bootstrap configuration for server specific fields.

	// Listener resource name template is mandatory on the server side.
	cfg := xdsClient.BootstrapConfig()
	if cfg.ServerListenerResourceNameTemplate() == "" {
		xdsClientClose()
		return nil, errors.New("missing server_listener_resource_name_template in the bootstrap configuration")
	}

	s.xdsC = xdsClient
	s.xdsClientClose = xdsClientClose

	s.logger = internalgrpclog.NewPrefixLogger(logger, fmt.Sprintf(serverPrefix, s))
	s.logger.Infof("Created xds.GRPCServer")

	return s, nil
}

// handleServerOptions iterates through the list of server options passed in by
// the user, and handles the xDS server specific options.
func (s *GRPCServer) handleServerOptions(opts []grpc.ServerOption) {
	so := s.defaultServerOptions()
	for _, opt := range opts {
		if o, ok := opt.(*serverOption); ok {
			o.apply(so)

View on GitHub (pinned to 0c51461d27)