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
- Keep only one entry under storage.trace_backends in the remote-storage config
- Move any additional backends to the main `jaeger` binary's config, which supports multiple backends
- 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
- Audit merged config fragments so they don't each add a backend
- Use remote-storage only for the single-backend serving role; multi-backend belongs to the main jaeger binary
- Add a startup-time count check on storage.trace_backends in deployment tooling
- Keep remote-storage configs minimal and generated from DefaultConfig() as a template
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
- invalid configuration: %w
- failed to unmarshal configuration: %w
- no sampling strategy provider specified, expecting 'adaptive
- only one sampling strategy provider can be specified, 'adapt
- reload interval must be a positive value, or zero to disable
AI-assisted analysis of jaegertracing/jaeger@806f444784 (2026-09-01).
Data as JSON: /api/errors/57a3e9d4f3e9e0ff.
Report an issue: GitHub.