jaegertracing/jaeger · error

remote-storage only supports a single storage backend, but %

Error message

remote-storage only supports a single storage backend, but %d were configured

What it means

Config.Validate enforces that the remote-storage service has exactly one storage backend. The remote-storage gRPC server serves a single trace backend, so unlike the main `jaeger` binary it rejects configurations that define multiple entries in storage.trace_backends, reporting how many were found.

Source

Thrown at cmd/remote-storage/app/config.go:53

	// Validate storage configuration
	if err := cfg.Validate(); err != nil {
		return nil, fmt.Errorf("invalid configuration: %w", err)
	}

	return cfg, nil
}

// Validate validates the configuration.
func (c *Config) Validate() error {
	// Validate storage configuration
	if err := c.Storage.Validate(); err != nil {
		return err
	}

	// Ensure only one backend is defined for remote-storage
	if len(c.Storage.TraceBackends) > 1 {
		return fmt.Errorf("remote-storage only supports a single storage backend, but %d were configured", len(c.Storage.TraceBackends))
	}

	return nil
}

// GetStorageName returns the name of the first configured storage backend.
// This is used as the default storage when not otherwise specified.
func (c *Config) GetStorageName() string {
	for name := range c.Storage.TraceBackends {
		return name
	}
	return ""
}

// DefaultConfig returns a default configuration with memory storage.
// This is used when no configuration file is provided.
func DefaultConfig() *Config {
	return &Config{

View on GitHub (pinned to 806f444784)

Solutions

  1. Keep only one entry under storage.trace_backends in the remote-storage config
  2. Move any additional backends to the main `jaeger` binary's config, which supports multiple backends
  3. If multiple configs are merged, scope viper keys so only the intended backend lands under storage.trace_backends

Example fix

# before (2 backends)
storage:
  trace_backends:
    memory: {memory: {max_traces: 1000000}}
    cassandra: {cassandra: {servers: [cassandra:9042]}}
# after (1 backend)
storage:
  trace_backends:
    memory: {memory: {max_traces: 1000000}}
Defensive patterns

Strategy: validation

Validate before calling

func ensureSingleBackend(v *viper.Viper) error {
    backends := v.GetStringMap("storage.trace_backends")
    if len(backends) > 1 {
        names := make([]string, 0, len(backends))
        for n := range backends { names = append(names, n) }
        return fmt.Errorf("remove extra backends, keep one of: %v", names)
    }
    return nil
}
// call before LoadConfigFromViper

Try / catch

if _, err := app.LoadConfigFromViper(v); err != nil {
    var cfgErr *fmt.WrapError // or match on message
    if strings.Contains(err.Error(), "single storage backend") {
        fmt.Fprintf(os.Stderr, "fix storage.trace_backends: %v\n", err)
        os.Exit(1)
    }
    return err
}

Prevention

When it happens

Trigger: Calling Validate (directly or via LoadConfigFromViper) when len(c.Storage.TraceBackends) > 1 — i.e. two or more named backends are present in the `storage.trace_backends` map.

Common situations: Reusing a main-jaeger config file that defines several backends (e.g. cassandra + opensearch) for the remote-storage binary; merging multiple config fragments whose backend maps combine into more than one entry.

Related errors


AI-assisted analysis of jaegertracing/jaeger@806f444784 (2026-09-01). Data as JSON: /api/errors/57a3e9d4f3e9e0ff. Report an issue: GitHub.