Repository: jaegertracing/jaeger

Issue:
[Feature]: Implement Scarf Tracking for Docs/Download
### Requirement

As a maintainer of Jaeger I would like to understand which companies and organizations are using or adopting our project so that we can track usage information

### Problem

This is a master tracking issue, I will open two issues related to it.

1. Docs - https://about.scarf.sh/post/track-your-projects-documentation-views
2. Downloads - https://about.scarf.sh/post/measure-your-open-source-projects-downloads-using-scarf


Hints from issue comments:

Solution patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 592b396a..79507db4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -21,6 +21,73 @@ copy from UI changelog
 
 </details>
 
+v1.70.0 / v2.7.0 (2025-06-10)
+-------------------------------
+
+### Backend Changes
+
+#### ✨ New Features
+
+* [feat] use v2 es/os storage in jaeger-v2 ([@Manik2708](https://github.com/Manik2708) in [#7151](https://github.com/jaegertracing/jaeger/pull/7151))
+
+#### 🐞 Bug fixes, Minor Improvements
+
+* Feat: add option to disable elasticsearch health check ([@timonegk](https://github.com/timonegk) in [#7212](https://github.com/jaegertracing/jaeger/pull/7212))
+* Fix(elasticsearch): respect explicitly configured replicas=0 in index… ([@masihkhatibzadeh99](https://github.com/masihkhatibzadeh99) in [#7160](https://github.com/jaegertracing/jaeger/pull/7160))
+* Add sanitizers for negative span duration ([@iypetrov](https://github.com/iypetrov) in [#7122](https://github.com/jaegertracing/jaeger/pull/7122))
+* [fix] fix prometheus label value is not valid utf 8 cause api timeout ([@iypetrov](https://github.com/iypetrov) in [#7128](https://github.com/jaegertracing/jaeger/pull/7128))
+* Add retries to ilm client ([@iypetrov](https://github.com/iypetrov) in [#7120](https://github.com/jaegertracing/jaeger/pull/7120))
+* Add retry configuration to storage exporter ([@kumarlokesh](https://github.com/kumarlokesh) in [#7132](https://github.com/jaegertracing/jaeger/pull/7132))
+* [fix] restore es metrics ([@AnmolxSingh](https://github.com/AnmolxSingh) in [#7006](https://github.com/jaegertracing/jaeger/pull/7006))
+* [fix] propagate environment variables to binary from integration tests ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7112](https://github.com/jaegertracing/jaeger/pull/7112))
+
+#### 🚧 Experimental Features
+
+* [refactor] rework clickhouse schema structure ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7181](https://github.com/jaegertracing/jaeger/pull/7181))
+* [clickhouse] implement getoperations for trace reader in clickhouse storage ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7180](https://github.com/jaegertracing/jaeger/pull/7180))
+* [clickhouse] implement getservices for trace reader in clickhouse storage ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7159](https://github.com/jaegertracing/jaeger/pull/7159))
+* [v2] implement `getdependencies` for memory backend ([@Manik2708](https://github.com/Manik2708) in [#7154](https://github.com/jaegertracing/jaeger/pull/7154))
+* [v2] implement `gettraces` for memory backend ([@Manik2708](https://github.com/Manik2708) in [#7152](https://github.com/jaegertracing/jaeger/pull/7152))
+* [v2] implement `findtraceids` for memory backend ([@Manik2708](https://github.com/Manik2708) in [#7143](https://github.com/jaegertracing/jaeger/pull/7143))
+* Add description for docker-compose-elasticsearch.yml ([@pipiland2612](https://github.com/pipiland2612) in [#7146](https://github.com/jaegertracing/jaeger/pull/7146))
+* Add e2e test for docker-compose-elasticsearch.yml file ([@pipiland2612](https://github.com/pipiland2612) in [#7145](https://github.com/jaegertracing/jaeger/pull/7145))
+* Add docker-compose-elasticsearch.yml and its sample configuration ([@pipiland2612](https://github.com/pipiland2612) in [#7144](https://github.com/jaegertracing/jaeger/pull/7144))
+* [v2] implement `findtraces` for memory backend ([@Manik2708](https://github.com/Manik2708) in [#7062](https://github.com/jaegertracing/jaeger/pull/7062))
+
+#### ⚙️ Refactoring
+
+* Relocate the docker directory from the root directory to under scripts/build ([@ris-tlp](https://github.com/ris-tlp) in [#7189](https://github.com/jaegertracing/jaeger/pull/7189))
+* [refactor] move sanitizer ([@yurishkuro](https://github.com/yurishkuro) in [#7158](https://github.com/jaegertracing/jaeger/pull/7158))
+* [es][v2] refactor the factory of v1 to make it reusable for v2 ([@Manik2708](https://github.com/Manik2708) in [#7086](https://github.com/jaegertracing/jaeger/pull/7086))
+* [refactor] allow storage cleaner to be overridden via environment variable ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7114](https://github.com/jaegertracing/jaeger/pull/7114))
+* [refactor] remove archive storage from grpc config ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7113](https://github.com/jaegertracing/jaeger/pull/7113))
+* [grpc] allow remote storage endpoint to be set via environment variable ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7111](https://github.com/jaegertracing/jaeger/pull/7111))
+* [refactor] use proto files from `jaeger-idl` for remote storage api ([@mahadzaryab1](https://github.com/mahadzaryab1) in [#7104](https://github.com/jaegertracing/jaeger/pull/7104))
+
+
+### 📊 UI Changes
+
+#### 🐞 Bug fixes, Minor Improvements
+
+* Fix react fragment key issues in multiple components ([@Parship999](https://github.com/Parship999) in [#2823](https://github.com/jaegertracing/jaeger-ui/pull/2823))
+* Move tracediff header chevron icon ([@Parship999](https://github.com/Parship999) in [#2845](https://github.com/jaegertracing/jaeger-ui/pull/2845))
+* Feat: filter logs based on the selected time range ([@tejas-raskar](https://github.com/tejas-raskar) in [#2844](https://github.com/jaegertracing/jaeger-ui/pull/2844))
+* Enhance tracediff ui components ([@Parship999](https://github.com/Parship999) in [#2806](https://github.com/jaegertracing/jaeger-ui/pull/2806))
+* Rewrite computeselftime to improve performance on trace statistics page ([@DamianMaslanka5](https://github.com/DamianMaslanka5) in [#2767](https://github.com/jaegertracing/jaeger-ui/pull/2767))
+* Fix array return pattern in `labeledlist` component ([@Parship999](https://github.com/Parship999) in [#2812](https://github.com/jaegertracing/jaeger-ui/pull/2812))
+* Allow json logs to occupy entire available width ([@tejas-raskar](https://github.com/tejas-raskar) in [#2814](https://github.com/jaegertracing/jaeger-ui/pull/2814))
+* Feat: convert monitoratmemptystate to a functional component ([@vishvamsinh28](https://github.com/vishvamsinh28) in [#2790](https://github.com/jaegertracing/jaeger-ui/pull/2790))
+* Replace deprecated `overlayclassname` with `classnames.root` ([@abhayporwals](https://github.com/abhayporwals) in [#2772](https://github.com/jaegertracing/jaeger-ui/pull/2772))
+* [fix]: reduce default minimum allowed zoom ([@hari45678](https://github.com/hari45678) in [#2775](https://github.com/jaegertracing/jaeger-ui/pull/2775))
+* Fix dependencygraph dag extra render ([@mdwyer6](https://github.com/mdwyer6) in [#2749](https://github.com/jaegertracing/jaeger-ui/pull/2749))
+
+#### ⚙️ Refactoring
+
+* Convert qualitymetrics components to functional components ([@Parship999](https://github.com/Parship999) in [#2856](https://github.com/jaegertracing/jaeger-ui/pull/2856))
+* Refactor: spandetailrow to functional component ([@tejas-raskar](https://github.com/tejas-raskar) in [#2827](https://github.com/jaegertracing/jaeger-ui/pull/2827))
+* Migrate tracetimelineviewerimpl to a functional component ([@tejas-raskar](https://github.com/tejas-raskar) in [#2816](https://github.com/jaegertracing/jaeger-ui/pull/2816))
+* Refactor canvasspangraph to functional component and improve test coverage ([@vishvamsinh28](https://github.com/vishvamsinh28) in [#2824](https://github.com/jaegertracing/jaeger-ui/pull/2824))
+
 v1.69.0 / v2.6.0 (2025-05-08)
 -------------------------------
 
diff --git a/RELEASE.md b/RELEASE.md
index 8e726622..445d80ac 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -88,9 +88,9 @@ Here are the release managers for future versions with the tentative release dat
 
 | Version | Release Manager | Tentative release date |
 |---------|-----------------|------------------------|
-| 1.70.0  | @joe-elliott    | 4 June      2025       |
 | 1.71.0  | @mahadzaryab1   | 2 July      2025       |
 | 1.72.0  | @jkowall        | 6 August    2025       |
 | 1.73.0  | @yurishkuro     | 5 September 2025       |
 | 1.74.0  | @albertteoh     | 1 October   2025       |
 | 1.75.0  | @pavolloffay    | 5 November  2025       |
+| 1.76.0  | @joe-elliott    | 3 December  2025       |
diff --git a/cmd/es-rollover/app/init/flags.go b/cmd/es-rollover/app/init/flags.go
index 36c40aa8..5aca4e7e 100644
--- a/cmd/es-rollover/app/init/flags.go
+++ b/cmd/es-rollover/app/init/flags.go
@@ -28,6 +28,10 @@ type Config struct {
 	cfg.Indices
 }
 
+func ptr[T any](v T) *T {
+	return &v
+}
+
 // AddFlags adds flags for TLS to the FlagSet.
 func (*Config) AddFlags(flags *flag.FlagSet) {
 	flags.Int(shards, 5, "Number of shards")
@@ -45,10 +49,11 @@ func (c *Config) InitFromViper(v *viper.Viper) {
 	c.Indices.Dependencies.Shards = v.GetInt64(shards)
 	c.Indices.Sampling.Shards = v.GetInt64(shards)
 
-	c.Indices.Spans.Replicas = v.GetInt64(replicas)
-	c.Indices.Services.Replicas = v.GetInt64(replicas)
-	c.Indices.Dependencies.Replicas = v.GetInt64(replicas)
-	c.Indices.Sampling.Replicas = v.GetInt64(replicas)
+	replicas := v.GetInt64(replicas)
+	c.Indices.Spans.Replicas = ptr(replicas)
+	c.Indices.Services.Replicas = ptr(replicas)
+	c.Indices.Dependencies.Replicas = ptr(replicas)
+	c.Indices.Sampling.Replicas = ptr(replicas)
 
 	c.Indices.Spans.Priority = v.GetInt64(prioritySpanTemplate)
 	c.Indices.Services.Priority = v.GetInt64(priorityServiceTemplate)
diff --git a/cmd/jaeger/config-spm-without-prometheus.yaml b/cmd/jaeger/config-spm-without-prometheus.yaml
new file mode 100644
index 00000000..5003fd8b
--- /dev/null
+++ b/cmd/jaeger/config-spm-without-prometheus.yaml
@@ -0,0 +1,38 @@
+service:
+  extensions: [jaeger_storage, jaeger_query]
+  pipelines:
+    traces:
+      receivers: [otlp]
+      processors: [batch]
+      exporters: [jaeger_storage_exporter]
+
+extensions:
+  jaeger_query:
+    storage:
+      traces: elasticsearch_trace_storage
+      metrics: elasticsearch_trace_storage
+  jaeger_storage:
+    backends:
+      elasticsearch_trace_storage:
+        elasticsearch:
+          server_urls:
+            - http://elasticsearch:9200
+    metric_backends:
+      elasticsearch_trace_storage:
+        elasticsearch:
+          server_urls:
+            - http://elasticsearch:9200
+
+receivers:
+  otlp:
+    protocols:
+      grpc:
+      http:
+        endpoint: "0.0.0.0:4318"
+
+processors:
+  batch:
+
+exporters:
+  jaeger_storage_exporter:
+    trace_storage: elasticsearch_trace_storage
diff --git a/cmd/jaeger/internal/extension/jaegerstorage/config.go b/cmd/jaeger/internal/extension/jaegerstorage/config.go
index 79412cc3..56a10fe8 100644
--- a/cmd/jaeger/internal/extension/jaegerstorage/config.go
+++ b/cmd/jaeger/internal/extension/jaegerstorage/config.go
@@ -51,7 +51,8 @@ type TraceBackend struct {
 
 // MetricBackend contains configuration for a single metric storage backend.
 type MetricBackend struct {
-	Prometheus *promCfg.Configuration `mapstructure:"prometheus"`
+	Prometheus    *promCfg.Configuration `mapstructure:"prometheus"`
+	Elasticsearch *esCfg.Configuration   `mapstructure:"elasticsearch"`
 }
 
 // Unmarshal implements confmap.Unmarshaler. This allows us to provide
@@ -116,5 +117,10 @@ func (cfg *MetricBackend) Unmarshal(conf *confmap.Conf) error {
 		v := prometheus.DefaultConfig()
 		cfg.Prometheus = &v
 	}
+
+	if conf.IsSet("elasticsearch") {
+		v := es.DefaultConfig()
+		cfg.Elasticsearch = &v
+	}
 	return conf.Unmarshal(cfg)
 }
diff --git a/cmd/jaeger/internal/extension/jaegerstorage/extension.go b/cmd/jaeger/internal/extension/jaegerstorage/extension.go
index 7cb38992..539a814c 100644
--- a/cmd/jaeger/internal/extension/jaegerstorage/extension.go
+++ b/cmd/jaeger/internal/extension/jaegerstorage/extension.go
@@ -13,6 +13,7 @@ import (
 	"go.opentelemetry.io/collector/extension"
 
 	"github.com/jaegertracing/jaeger/internal/metrics"
+	esmetrics "github.com/jaegertracing/jaeger/internal/storage/metricstore/elasticsearch"
 	"github.com/jaegertracing/jaeger/internal/storage/metricstore/prometheus"
 	"github.com/jaegertracing/jaeger/internal/storage/v1"
 	"github.com/jaegertracing/jaeger/internal/storage/v1/badger"
@@ -212,19 +213,28 @@ func (s *storageExt) Start(ctx context.Context, host component.Host) error {
 
 	for metricStorageName, cfg := range s.config.MetricBackends {
 		s.telset.Logger.Sugar().Infof("Initializing metrics storage '%s'", metricStorageName)
-		var metricsFactory storage.MetricStoreFactory
+		var metricStoreFactory storage.MetricStoreFactory
 		var err error
-		if cfg.Prometheus != nil {
+		switch {
+		case cfg.Prometheus != nil:
 			promTelset := telset
 			promTelset.Metrics = scopedMetricsFactory(metricStorageName, "prometheus", "metricstore")
-			metricsFactory, err = prometheus.NewFactoryWithConfig(
+			metricStoreFactory, err = prometheus.NewFactoryWithConfig(
 				*cfg.Prometheus,
 				promTelset)
+
+		case cfg.Elasticsearch != nil:
+			esTelset := telset
+			esTelset.Metrics = scopedMetricsFactory(metricStorageName, "elasticsearch", "metricstore")
+			metricStoreFactory, err = esmetrics.NewFactory(
+				*cfg.Elasticsearch,
+				esTelset,
+			)
 		}
 		if err != nil {
 			return fmt.Errorf("failed to initialize metrics storage '%s': %w", metricStorageName, err)
 		}
-		s.metricsFactories[metricStorageName] = metricsFactory
+		s.metricsFactories[metricStorageName] = metricStoreFactory
 	}
 
 	return nil
diff --git a/docs/SCARF.md b/docs/SCARF.md
new file mode 100644
index 00000000..f2fc1142
--- /dev/null
+++ b/docs/SCARF.md
@@ -0,0 +1,77 @@
+# Setup for Scarf
+
+This document outlines our implementation details for [Scarf](https://scarf.sh) which provides usage and download analytics for the Jaeger project. 
+
+## DNS Configuration
+The following CNAMES were setup in Netlify for us to utilize the services:
+
+  * `scarf.jaegertracing.io` -> `static.scarf.sh` (used for tracking pixel on webpages)
+  * `cr.jaegertracing.io` -> `gateway.scarf.sh` (used for container registries)
+  * `download.jaegertracing.io` -> `gateway.scarf.sh` (used for file downloads of Jaeger artifacts)
+
+We also had to add the following TXT verification records:
+1. _scarf-sh-challenge-jaeger.cr.jaegertracing.io - ZN4RLVE3CENVUIXDBNYCa
+2. _scarf-sh-challenge-jaeger.download.jaegertracing.io - U2GBROI64YGH2JLRTXPI
+3. _scarf-sh-challenge-jaeger.scarf.jaegertracing.io - AKB26262A53WP55R4EXR
+
+## Download and Docker Configuration
+The following setup has been done on Scarf. Previously what was the download link for example https://github.com/jaegertracing/jaeger/releases/download/v1.69.0/jaeger-2.6.0-darwin-amd64.tar.gz should now be https://download.jaegertracing.io/v1.69.0/jaeger-2.6.0-darwin-amd64.tar.gz for us to get analytics.
+
+For Docker containers the previous command : docker pull jaegertracing/all-in-one should now be docker pull cr.jaegertracing.io/jaegertracing/all-in-one
+
+# Integrating Scarf Analytics with `www.jaegertracing.io` on Netlify
+
+Injecting the Scarf analytics tracking pixel to the `www.jaegertracing.io` website hosted on Netlify is done using Netlify's **Snippet Injection** feature.
+
+## Steps to Add the Scarf Pixel
+
+1.  **Log in to Netlify:**
+
+      * Head over to [app.netlify.com](https://app.netlify.com/) and sign in to your account which has access to the `www.jaegertracing.io` site configuration.
+
+2.  **Select Your Site:**
+
+      * From the list of sites, click on `www.jaegertracing.io`.
+
+3.  **Go to Project Configuration:**
+
+      * On the `www.jaegertracing.io` site dashboard, click on **"Site settings"** (you'll usually find this at the top right).
+
+4.  **Access Build & Deploy Settings:**
+
+      * In the left-hand sidebar, under "Site settings," click on **"Build & deploy."**
+
+5.  **Find Snippet Injection:**
+
+      * Scroll down to the "Post processing" section and find the **"Snippet Injection"** section.
+      * Click the **"Add Snippet"** button.
+
+6.  **Configure the Scarf Snippet:**
+
+      * A form will pop up for your new snippet:
+          * **Snippet Name:** Give it a clear name like `Scarf Tracking Pixel`.
+
+          * **Position:** Choose **"Before `</body>`"**. This is generally a good spot for image-based pixels as it doesn't block the initial page render.
+
+          * **Snippet Body:** Paste the following HTML code block into this text area. **The pixel ID `cf7517a5-bfa0-4796-b760-1bb4e302e541` is already included.**
+
+            ```html
+            <img referrerpolicy="no-referrer-when-downgrade" src="https://scarf.jaegertracing.io/a.png?x-pxid=cf7517a5-bfa0-4796-b760-1bb4e302e541" alt="" style="position: absolute; width: 0; height: 0; border: 0;" />
+            ```
+
+7.  **Save the Snippet:**
+
+      * Click the **"Save"** button to apply the changes.
+
+-----
+
+## Verification
+
+Once you save, Netlify will automatically inject this image tag into the HTML pages of `www.jaegertracing.io`. You won't need to trigger a new deployment. Scarf.sh should start receiving analytics data from the website shortly after.
+
+To verify the pixel is loading correctly:
+
+  * Visit `www.jaegertracing.io` in a browser.
+  * Open your browser's **developer tools** (usually by pressing F12 or right-clicking and selecting "Inspect").
+  * Go to the **"Network"** tab and filter by "a.png". You should see requests being made to `https://scarf.jaegertracing.io/a.png`.
+  
\ No newline at end of file
diff --git a/internal/storage/elasticsearch/config/config.go b/internal/storage/elasticsearch/config/config.go
index a56211b6..d006aba3 100644
--- a/internal/storage/elasticsearch/config/config.go
+++ b/internal/storage/elasticsearch/config/config.go
@@ -49,7 +49,7 @@ type IndexOptions struct {
 	// Shards is the number of shards per index in Elasticsearch.
 	Shards int64 `mapstructure:"shards"`
 	// Replicas is the number of replicas per index in Elasticsearch.
-	Replicas int64 `mapstructure:"replicas"`
+	Replicas *int64 `mapstructure:"replicas"`
 	// RolloverFrequency contains the rollover frequency setting used to fetch
 	// indices from elasticsearch.
 	// Valid configuration options are: [hour, day].
@@ -101,6 +101,8 @@ type Configuration struct {
 	// TLS contains the TLS configuration for the connection to the ElasticSearch clusters.
 	TLS      configtls.ClientConfig `mapstructure:"tls"`
 	Sniffing Sniffing               `mapstructure:"sniffing"`
+	// Disable the Elasticsearch health check
+	DisableHealthCheck bool `mapstructure:"disable_health_check"`
 	// SendGetBodyAs is the HTTP verb to use for requests that contain a body.
 	SendGetBodyAs string `mapstructure:"send_get_body_as"`
 	// QueryTimeout contains the timeout used for queries. A timeout of zero means no timeout.
@@ -355,7 +357,7 @@ func setDefaultIndexOptions(target, source *IndexOptions) {
 		target.Shards = source.Shards
 	}
 
-	if target.Replicas == 0 {
+	if target.Replicas == nil {
 		target.Replicas = source.Replicas
 	}
 
@@ -482,12 +484,14 @@ func (c *Configuration) TagKeysAsFields() ([]string, error) {
 
 // getConfigOptions wraps the configs to feed to the ElasticSearch client init
 func (c *Configuration) getConfigOptions(logger *zap.Logger) ([]elastic.ClientOptionFunc, error) {
+	// Disable health check when token from context is allowed, this is because at this time
+	// we don'r have a valid token to do the check ad if we don't disable the check the service that
+	// uses this won't start.
+	// Also disable health check when it was requested (health check has problems on AWS OpenSearch)
+	disableHealthCheck := c.Authentication.BearerTokenAuthentication.AllowFromContext || c.DisableHealthCheck
 	options := []elastic.ClientOptionFunc{
 		elastic.SetURL(c.Servers...), elastic.SetSniff(c.Sniffing.Enabled),
-		// Disable health check when token from context is allowed, this is because at this time
-		// we don'r have a valid token to do the check ad if we don't disable the check the service that
-		// uses this won't start.
-		elastic.SetHealthcheck(!c.Authentication.BearerTokenAuthentication.AllowFromContext),
+		elastic.SetHealthcheck(!disableHealthCheck),
 	}
 	if c.Sniffing.UseHTTPS {
 		options = append(options, elastic.SetScheme("https"))
diff --git a/internal/storage/metricstore/elasticsearch/factory.go b/internal/storage/metricstore/elasticsearch/factory.go
new file mode 100644
index 00000000..591e5109
--- /dev/null
+++ b/internal/storage/metricstore/elasticsearch/factory.go
@@ -0,0 +1,37 @@
+// Copyright (c) 2025 The Jaeger Authors.
+// SPDX-License-Identifier: Apache-2.0
+
+package elasticsearch
+
+import (
+	"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/config"
+	"github.com/jaegertracing/jaeger/internal/storage/v1/api/metricstore"
+	"github.com/jaegertracing/jaeger/internal/storage/v1/api/metricstore/metricstoremetrics"
+	"github.com/jaegertracing/jaeger/internal/telemetry"
+)
+
+type Factory struct {
+	config config.Configuration
+	telset telemetry.Settings
+}
+
+// NewFactory creates a new Factory with the given configuration and telemetry settings.
+func NewFactory(cfg config.Configuration, telset telemetry.Settings) (*Factory, error) {
+	if err := cfg.Validate(); err != nil {
+		return nil, err
+	}
+	return &Factory{
+		config: cfg,
+		telset: telset,
+	}, nil
+}
+
+// CreateMetricsReader implements storage.MetricStoreFactory.
+func (f *Factory) CreateMetricsReader() (metricstore.Reader, error) {
+	mr, _ := NewMetricsReader(f.config, f.telset.Logger, f.telset.TracerProvider)
+	// Currently, the NewMetricsReader function does not return an error.
+	// if err != nil {
+	//	 return nil, err
+	// }
+	return metricstoremetrics.NewReaderDecorator(mr, f.telset.Metrics), nil
+}
diff --git a/internal/storage/metricstore/elasticsearch/reader.go b/internal/storage/metricstore/elasticsearch/reader.go
new file mode 100644
index 00000000..eb421c18
--- /dev/null
+++ b/internal/storage/metricstore/elasticsearch/reader.go
@@ -0,0 +1,50 @@
+// Copyright (c) 2025 The Jaeger Authors.
+// SPDX-License-Identifier: Apache-2.0
+
+package elasticsearch
+
+import (
+	"context"
+	"errors"
+	"time"
+
+	"go.opentelemetry.io/otel/trace"
+	"go.uber.org/zap"
+
+	"github.com/jaegertracing/jaeger/internal/proto-gen/api_v2/metrics"
+	"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/config"
+	"github.com/jaegertracing/jaeger/internal/storage/v1/api/metricstore"
+)
+
+var ErrNotImplemented = errors.New("metrics querying is currently not implemented yet")
+
+const minStep = time.Millisecond
+
+// MetricsReader is a Elasticsearch metrics reader.
+type MetricsReader struct {
+	logger *zap.Logger
+	tracer trace.Tracer
+}
+
+func NewMetricsReader(_ config.Configuration, logger *zap.Logger, tracer trace.TracerProvider) (*MetricsReader, error) {
+	return &MetricsReader{
+		logger: logger,
+		tracer: tracer.Tracer("elasticsearch-metricstore"),
+	}, nil
+}
+
+func (MetricsReader) GetLatencies(_ context.Context, _ *metricstore.LatenciesQueryParameters) (*metrics.MetricFamily, error) {
+	return nil, ErrNotImplemented
+}
+
+func (MetricsReader) GetCallRates(_ context.Context, _ *metricstore.CallRateQueryParameters) (*metrics.MetricFamily, error) {
+	return nil, ErrNotImplemented
+}
+
+func (MetricsReader) GetErrorRates(_ context.Context, _ *metricstore.ErrorRateQueryParameters) (*metrics.MetricFamily, error) {
+	return nil, ErrNotImplemented
+}
+
+func (MetricsReader) GetMinStepDuration(_ context.Context, _ *metricstore.MinStepDurationQueryParameters) (time.Duration, error) {
+	return minStep, nil
+}
diff --git a/internal/storage/metricstore/factory.go b/internal/storage/metricstore/factory.go
index 043ab9ef..369e51c8 100644
--- a/internal/storage/metricstore/factory.go
+++ b/internal/storage/metricstore/factory.go
@@ -33,7 +33,7 @@ var _ storage.Configurable = (*Factory)(nil)
 // Factory implements storage.Factory interface as a meta-factory for storage components.
 type Factory struct {
 	FactoryConfig
-	factories map[string]storage.MetricStoreFactory
+	factories map[string]storage.V1MetricStoreFactory
 }
 
 // NewFactory creates the meta-factory.
@@ -42,7 +42,7 @@ func NewFactory(config FactoryConfig) (*Factory, error) {
 	uniqueTypes := map[string]struct{}{
 		f.MetricsStorageType: {},
 	}
-	f.factories = make(map[string]storage.MetricStoreFactory)
+	f.factories = make(map[string]storage.V1MetricStoreFactory)
 	for t := range uniqueTypes {
 		ff, err := f.getFactoryOfType(t)
 		if err != nil {
@@ -53,7 +53,7 @@ func NewFactory(config FactoryConfig) (*Factory, error) {
 	return f, nil
 }
 
-func (*Factory) getFactoryOfType(factoryType string) (storage.MetricStoreFactory, error) {
+func (*Factory) getFactoryOfType(factoryType string) (storage.V1MetricStoreFactory, error) {
 	switch factoryType {
 	case prometheusStorageType:
 		return prometheus.NewFactory(), nil
@@ -63,7 +63,7 @@ func (*Factory) getFactoryOfType(factoryType string) (storage.MetricStoreFactory
 	return nil, fmt.Errorf("unknown metrics type %q. Valid types are %v", factoryType, AllStorageTypes)
 }
 
-// Initialize implements storage.MetricsFactory.
+// Initialize implements storage.V1MetricStoreFactory.
 func (f *Factory) Initialize(telset telemetry.Settings) error {
 	for kind, factory := range f.factories {
 		scopedTelset := telset
@@ -79,7 +79,7 @@ func (f *Factory) Initialize(telset telemetry.Settings) error {
 	return nil
 }
 
-// CreateMetricsReader implements storage.MetricsFactory.
+// CreateMetricsReader implements storage.MetricStoreFactory.
 func (f *Factory) CreateMetricsReader() (metricstore.Reader, error) {
 	factory, ok := f.factories[f.MetricsStorageType]
 	if !ok {
diff --git a/internal/storage/metricstore/prometheus/factory.go b/internal/storage/metricstore/prometheus/factory.go
index 43a2c214..65d567ad 100644
--- a/internal/storage/metricstore/prometheus/factory.go
+++ b/internal/storage/metricstore/prometheus/factory.go
@@ -46,13 +46,13 @@ func (f *Factory) InitFromViper(v *viper.Viper, logger *zap.Logger) {
 	}
 }
 
-// Initialize implements storage.MetricsFactory.
+// Initialize implements storage.V1MetricStoreFactory.
 func (f *Factory) Initialize(telset telemetry.Settings) error {
 	f.telset = telset
 	return nil
 }
 
-// CreateMetricsReader implements storage.MetricsFactory.
+// CreateMetricsReader implements storage.V1MetricStoreFactory.
 func (f *Factory) CreateMetricsReader() (metricstore.Reader, error) {
 	mr, err := prometheusstore.NewMetricsReader(f.options.Configuration, f.telset.Logger, f.telset.TracerProvider)
 	if err != nil {
diff --git a/internal/storage/v1/elasticsearch/mappings/command.go b/internal/storage/v1/elasticsearch/mappings/command.go
index 0e81cdec..d36816be 100644
--- a/internal/storage/v1/elasticsearch/mappings/command.go
+++ b/internal/storage/v1/elasticsearch/mappings/command.go
@@ -52,7 +52,6 @@ func getMappingAsString(builder es.TemplateBuilder, opt Options) (string, error)
 	if err != nil {
 		return "", err
 	}
-
 	indexOpts := config.IndexOptions{
 		Shards:   opt.Shards,
 		Replicas: opt.Replicas,
diff --git a/internal/storage/v1/elasticsearch/mappings/flags.go b/internal/storage/v1/elasticsearch/mappings/flags.go
index 604c4306..86c569c1 100644
--- a/internal/storage/v1/elasticsearch/mappings/flags.go
+++ b/internal/storage/v1/elasticsearch/mappings/flags.go
@@ -12,7 +12,7 @@ type Options struct {
 	Mapping       string
 	EsVersion     uint
 	Shards        int64
-	Replicas      int64
+	Replicas      *int64
 	IndexPrefix   string
 	UseILM        string // using string as util is being used in python and using bool leads to type issues.
 	ILMPolicyName string
@@ -45,8 +45,10 @@ func (o *Options) AddFlags(command *cobra.Command) {
 		shardsFlag,
 		5,
 		"The number of shards per index in Elasticsearch")
+	// Allocate storage for Replicas so Int64Var can write into it.
+	o.Replicas = new(int64)
 	command.Flags().Int64Var(
-		&o.Replicas,
+		o.Replicas,
 		replicasFlag,
 		1,
 		"The number of replicas per index in Elasticsearch")
diff --git a/internal/storage/v1/elasticsearch/mappings/mapping.go b/internal/storage/v1/elasticsearch/mappings/mapping.go
index 1a0a3757..ed30e799 100644
--- a/internal/storage/v1/elasticsearch/mappings/mapping.go
+++ b/internal/storage/v1/elasticsearch/mappings/mapping.go
@@ -54,19 +54,19 @@ func (mb MappingBuilder) getMappingTemplateOptions(mappingType MappingType) temp
 	switch mappingType {
 	case SpanMapping:
 		mappingOpts.Shards = mb.Indices.Spans.Shards
-		mappingOpts.Replicas = mb.Indices.Spans.Replicas
+		mappingOpts.Replicas = *mb.Indices.Spans.Replicas
 		mappingOpts.Priority = mb.Indices.Spans.Priority
 	case ServiceMapping:
 		mappingOpts.Shards = mb.Indices.Services.Shards
-		mappingOpts.Replicas = mb.Indices.Services.Replicas
+		mappingOpts.Replicas = *mb.Indices.Services.Replicas
 		mappingOpts.Priority = mb.Indices.Services.Priority
 	case DependenciesMapping:
 		mappingOpts.Shards = mb.Indices.Dependencies.Shards
-		mappingOpts.Replicas = mb.Indices.Dependencies.Replicas
+		mappingOpts.Replicas = *mb.Indices.Dependencies.Replicas
 		mappingOpts.Priority = mb.Indices.Dependencies.Priority
 	case SamplingMapping:
 		mappingOpts.Shards = mb.Indices.Sampling.Shards
-		mappingOpts.Replicas = mb.Indices.Sampling.Replicas
+		mappingOpts.Replicas = *mb.Indices.Sampling.Replicas
 		mappingOpts.Priority = mb.Indices.Sampling.Priority
 	}
 
diff --git a/internal/storage/v1/elasticsearch/options.go b/internal/storage/v1/elasticsearch/options.go
index 47f84f6c..01bcd9be 100644
--- a/internal/storage/v1/elasticsearch/options.go
+++ b/internal/storage/v1/elasticsearch/options.go
@@ -21,6 +21,7 @@ const (
 	suffixUsername                       = ".username"
 	suffixPassword                       = ".password"
 	suffixSniffer                        = ".sniffer"
+	suffixDisableHealthCheck             = ".disable-health-check"
 	suffixSnifferTLSEnabled              = ".sniffer-tls-enabled"
 	suffixTokenPath                      = ".token-file"
 	suffixPasswordPath                   = ".password-file"
@@ -75,7 +76,7 @@ var defaultIndexOptions = config.IndexOptions{
 	DateLayout:        initDateLayout(defaultIndexRolloverFrequency, defaultIndexDateSeparator),
 	RolloverFrequency: defaultIndexRolloverFrequency,
 	Shards:            5,
-	Replicas:          1,
+	Replicas:          ptr(int64(1)),
 	Priority:          0,
 }
 
@@ -114,6 +115,20 @@ func (cfg *namespaceConfig) getTLSFlagsConfig() tlscfg.ClientFlagsConfig {
 	}
 }
 
+// ptr returns a pointer to the given value.
+func ptr[T any](v T) *T {
+	return &v
+}
+
+// safeDerefInt64 safely dereferences a *int64 for use in flagSet.Int64.
+// If the pointer is nil (meaning no config was set), returns 0 as neutral default.
+func safeDerefInt64(ptr *int64) int64 {
+	if ptr != nil {
+		return *ptr
+	}
+	return 0
+}
+
 // AddFlags adds flags for Options
 func (opt *Options) AddFlags(flagSet *flag.FlagSet) {
 	addFlags(flagSet, &opt.Config)
@@ -140,6 +155,10 @@ func addFlags(flagSet *flag.FlagSet, nsConfig *namespaceConfig) {
 		nsConfig.namespace+suffixSniffer,
 		nsConfig.Sniffing.Enabled,
 		"The sniffer config for Elasticsearch; client uses sniffing process to find all nodes automatically, disable if not required")
+	flagSet.Bool(
+		nsConfig.namespace+suffixDisableHealthCheck,
+		nsConfig.DisableHealthCheck,
+		"Disable the Elasticsearch health check.")
 	flagSet.String(
 		nsConfig.namespace+suffixServerURLs,
 		defaultServerURL,
@@ -164,7 +183,7 @@ func addFlags(flagSet *flag.FlagSet, nsConfig *namespaceConfig) {
 	)
 	flagSet.Int64(
 		nsConfig.namespace+suffixNumReplicas,
-		nsConfig.Indices.Spans.Replicas,
+		safeDerefInt64(nsConfig.Indices.Spans.Replicas),
 		"The number of replicas per index in Elasticsearch")
 	flagSet.Int64(
 		nsConfig.namespace+suffixPrioritySpanTemplate,
@@ -305,6 +324,7 @@ func initFromViper(cfg *namespaceConfig, v *viper.Viper) {
 	cfg.Authentication.BasicAuthentication.PasswordFilePath = v.GetString(cfg.namespace + suffixPasswordPath)
 	cfg.Sniffing.Enabled = v.GetBool(cfg.namespace + suffixSniffer)
 	cfg.Sniffing.UseHTTPS = v.GetBool(cfg.namespace + suffixSnifferTLSEnabled)
+	cfg.DisableHealthCheck = v.GetBool(cfg.namespace + suffixDisableHealthCheck)
 	cfg.Servers = strings.Split(stripWhiteSpace(v.GetString(cfg.namespace+suffixServerURLs)), ",")
 	cfg.MaxSpanAge = v.GetDuration(cfg.namespace + suffixMaxSpanAge)
 	cfg.AdaptiveSamplingLookback = v.GetDuration(cfg.namespace + suffixAdaptiveSamplingLookback)
@@ -314,10 +334,13 @@ func initFromViper(cfg *namespaceConfig, v *viper.Viper) {
 	cfg.Indices.Sampling.Shards = v.GetInt64(cfg.namespace + suffixNumShards)
 	cfg.Indices.Dependencies.Shards = v.GetInt64(cfg.namespace + suffixNumShards)
 
-	cfg.Indices.Spans.Replicas = v.GetInt64(cfg.namespace + suffixNumReplicas)
-	cfg.Indices.Services.Replicas = v.GetInt64(cfg.namespace + suffixNumReplicas)
-	cfg.Indices.Sampling.Replicas = v.GetInt64(cfg.namespace + suffixNumReplicas)
-	cfg.Indices.Dependencies.Replicas = v.GetInt64(cfg.namespace + suffixNumReplicas)
+	// Note: We use a pointer type for Replicas to distinguish between "unset" and "explicit 0".
+	// Each field receives its own pointer to avoid accidental shared state.
+	replicas := v.GetInt64(cfg.namespace + suffixNumReplicas)
+	cfg.Indices.Spans.Replicas = ptr(replicas)
+	cfg.Indices.Services.Replicas = ptr(replicas)
+	cfg.Indices.Sampling.Replicas = ptr(replicas)
+	cfg.Indices.Dependencies.Replicas = ptr(replicas)
 
 	cfg.Indices.Spans.Priority = v.GetInt64(cfg.namespace + suffixPrioritySpanTemplate)
 	cfg.Indices.Services.Priority = v.GetInt64(cfg.namespace + suffixPriorityServiceTemplate)
@@ -406,6 +429,7 @@ func DefaultConfig() config.Configuration {
 		Sniffing: config.Sniffing{
 			Enabled: false,
 		},
+		DisableHealthCheck:       false,
 		MaxSpanAge:               72 * time.Hour,
 		AdaptiveSamplingLookback: 72 * time.Hour,
 		BulkProcessing: config.BulkProcessing{
diff --git a/internal/storage/v1/factory.go b/internal/storage/v1/factory.go
index 40a2e434..642233b2 100644
--- a/internal/storage/v1/factory.go
+++ b/internal/storage/v1/factory.go
@@ -61,18 +61,21 @@ type SamplingStoreFactory interface {
 }
 
 // MetricStoreFactory defines an interface for a factory that can create implementations of different metrics storage components.
-// Implementations are also encouraged to implement storage.Configurable interface.
+type MetricStoreFactory interface {
+	CreateMetricsReader() (metricstore.Reader, error)
+}
+
+// V1MetricStoreFactory is a v1 version of MetricStoreFactory.
+// Implementations are encouraged to implement storage.Configurable interface.
 //
 // # See also
 //
 // storage.Configurable
-type MetricStoreFactory interface {
+type V1MetricStoreFactory interface {
+	MetricStoreFactory
 	// Initialize performs internal initialization of the factory, such as opening connections to the backend store.
 	// It is called after all configuration of the factory itself has been done.
 	Initialize(telset telemetry.Settings) error
-
-	// CreateMetricsReader creates a metricstore.Reader.
-	CreateMetricsReader() (metricstore.Reader, error)
 }
 
 // Inheritable is an interface that can be implement by some storage implementations
diff --git a/internal/storage/v1/mocks/mocks.go b/internal/storage/v1/mocks/mocks.go
index bc83d6dc..b7bcde65 100644
--- a/internal/storage/v1/mocks/mocks.go
+++ b/internal/storage/v1/mocks/mocks.go
@@ -883,8 +883,90 @@ func (_c *MetricStoreFactory_CreateMetricsReader_Call) RunAndReturn(run func() (
 	return _c
 }
 
-// Initialize provides a mock function for the type MetricStoreFactory
-func (_mock *MetricStoreFactory) Initialize(telset telemetry.Settings) error {
+// NewV1MetricStoreFactory creates a new instance of V1MetricStoreFactory. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+// The first argument is typically a *testing.T value.
+func NewV1MetricStoreFactory(t interface {
+	mock.TestingT
+	Cleanup(func())
+}) *V1MetricStoreFactory {
+	mock := &V1MetricStoreFactory{}
+	mock.Mock.Test(t)
+
+	t.Cleanup(func() { mock.AssertExpectations(t) })
+
+	return mock
+}
+
+// V1MetricStoreFactory is an autogenerated mock type for the V1MetricStoreFactory type
+type V1MetricStoreFactory struct {
+	mock.Mock
+}
+
+type V1MetricStoreFactory_Expecter struct {
+	mock *mock.Mock
+}
+
+func (_m *V1MetricStoreFactory) EXPECT() *V1MetricStoreFactory_Expecter {
+	return &V1MetricStoreFactory_Expecter{mock: &_m.Mock}
+}
+
+// CreateMetricsReader provides a mock function for the type V1MetricStoreFactory
+func (_mock *V1MetricStoreFactory) CreateMetricsReader() (metricstore.Reader, error) {
+	ret := _mock.Called()
+
+	if len(ret) == 0 {
+		panic("no return value specified for CreateMetricsReader")
+	}
+
+	var r0 metricstore.Reader
+	var r1 error
+	if returnFunc, ok := ret.Get(0).(func() (metricstore.Reader, error)); ok {
+		return returnFunc()
+	}
+	if returnFunc, ok := ret.Get(0).(func() metricstore.Reader); ok {
+		r0 = returnFunc()
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(metricstore.Reader)
+		}
+	}
+	if returnFunc, ok := ret.Get(1).(func() error); ok {
+		r1 = returnFunc()
+	} else {
+		r1 = ret.Error(1)
+	}
+	return r0, r1
+}
+
+// V1MetricStoreFactory_CreateMetricsReader_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateMetricsReader'
+type V1MetricStoreFactory_CreateMetricsReader_Call struct {
+	*mock.Call
+}
+
+// CreateMetricsReader is a helper method to define mock.On call
+func (_e *V1MetricStoreFactory_Expecter) CreateMetricsReader() *V1MetricStoreFactory_CreateMetricsReader_Call {
+	return &V1MetricStoreFactory_CreateMetricsReader_Call{Call: _e.mock.On("CreateMetricsReader")}
+}
+
+func (_c *V1MetricStoreFactory_CreateMetricsReader_Call) Run(run func()) *V1MetricStoreFactory_CreateMetricsReader_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run()
+	})
+	return _c
+}
+
+func (_c *V1MetricStoreFactory_CreateMetricsReader_Call) Return(reader metricstore.Reader, err error) *V1MetricStoreFactory_CreateMetricsReader_Call {
+	_c.Call.Return(reader, err)
+	return _c
+}
+
+func (_c *V1MetricStoreFactory_CreateMetricsReader_Call) RunAndReturn(run func() (metricstore.Reader, error)) *V1MetricStoreFactory_CreateMetricsReader_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
+// Initialize provides a mock function for the type V1MetricStoreFactory
+func (_mock *V1MetricStoreFactory) Initialize(telset telemetry.Settings) error {
 	ret := _mock.Called(telset)
 
 	if len(ret) == 0 {
@@ -900,18 +982,18 @@ func (_mock *MetricStoreFactory) Initialize(telset telemetry.Settings) error {
 	return r0
 }
 
-// MetricStoreFactory_Initialize_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Initialize'
-type MetricStoreFactory_Initialize_Call struct {
+// V1MetricStoreFactory_Initialize_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Initialize'
+type V1MetricStoreFactory_Initialize_Call struct {
 	*mock.Call
 }
 
 // Initialize is a helper method to define mock.On call
 //   - telset telemetry.Settings
-func (_e *MetricStoreFactory_Expecter) Initialize(telset interface{}) *MetricStoreFactory_Initialize_Call {
-	return &MetricStoreFactory_Initialize_Call{Call: _e.mock.On("Initialize", telset)}
+func (_e *V1MetricStoreFactory_Expecter) Initialize(telset interface{}) *V1MetricStoreFactory_Initialize_Call {
+	return &V1MetricStoreFactory_Initialize_Call{Call: _e.mock.On("Initialize", telset)}
 }
 
-func (_c *MetricStoreFactory_Initialize_Call) Run(run func(telset telemetry.Settings)) *MetricStoreFactory_Initialize_Call {
+func (_c *V1MetricStoreFactory_Initialize_Call) Run(run func(telset telemetry.Settings)) *V1MetricStoreFactory_Initialize_Call {
 	_c.Call.Run(func(args mock.Arguments) {
 		var arg0 telemetry.Settings
 		if args[0] != nil {
@@ -924,12 +1006,12 @@ func (_c *MetricStoreFactory_Initialize_Call) Run(run func(telset telemetry.Sett
 	return _c
 }
 
-func (_c *MetricStoreFactory_Initialize_Call) Return(err error) *MetricStoreFactory_Initialize_Call {
+func (_c *V1MetricStoreFactory_Initialize_Call) Return(err error) *V1MetricStoreFactory_Initialize_Call {
 	_c.Call.Return(err)
 	return _c
 }
 
-func (_c *MetricStoreFactory_Initialize_Call) RunAndReturn(run func(telset telemetry.Settings) error) *MetricStoreFactory_Initialize_Call {
+func (_c *V1MetricStoreFactory_Initialize_Call) RunAndReturn(run func(telset telemetry.Settings) error) *V1MetricStoreFactory_Initialize_Call {
 	_c.Call.Return(run)
 	return _c
 }
diff --git a/jaeger-ui b/jaeger-ui
index dc095a78..5b05b9de 160000
--- a/jaeger-ui
+++ b/jaeger-ui
@@ -1 +1 @@
-Subproject commit dc095a78cede1eb1dfea7d68a0dbbe4b32fbb9f4
+Subproject commit 5b05b9ded1c7f49a76e546a8173676aae5910a83



Test patch:
diff --git a/cmd/es-rollover/app/init/action_test.go b/cmd/es-rollover/app/init/action_test.go
index afe53c25..4b3b9acf 100644
--- a/cmd/es-rollover/app/init/action_test.go
+++ b/cmd/es-rollover/app/init/action_test.go
@@ -16,6 +16,19 @@ import (
 	"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/client/mocks"
 )
 
+func applyTestDefaults(cfg *Config) {
+	// Set defaults only if missing
+	if cfg.Indices.Spans.Shards == 0 {
+		cfg.Indices.Spans.Shards = 3
+	}
+	if cfg.Indices.Spans.Replicas == nil {
+		cfg.Indices.Spans.Replicas = ptr(int64(1))
+	}
+	if cfg.Indices.Spans.Priority == 0 {
+		cfg.Indices.Spans.Priority = 10
+	}
+}
+
 func TestIndexCreateIfNotExist(t *testing.T) {
 	tests := []struct {
 		name           string
@@ -235,6 +248,8 @@ func TestRolloverAction(t *testing.T) {
 
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
+			// Apply local test defaults
+			applyTestDefaults(&test.config)
 			indexClient := &mocks.IndexAPI{}
 			clusterClient := &mocks.ClusterAPI{}
 			ilmClient := &mocks.IndexManagementLifecycleAPI{}
diff --git a/cmd/es-rollover/app/init/flags_test.go b/cmd/es-rollover/app/init/flags_test.go
index 3409cffd..4d76c42d 100644
--- a/cmd/es-rollover/app/init/flags_test.go
+++ b/cmd/es-rollover/app/init/flags_test.go
@@ -34,7 +34,8 @@ func TestBindFlags(t *testing.T) {
 
 	c.InitFromViper(v)
 	assert.EqualValues(t, 8, c.Indices.Spans.Shards)
-	assert.EqualValues(t, 16, c.Indices.Spans.Replicas)
+	require.NotNil(t, c.Indices.Spans.Replicas)
+	assert.EqualValues(t, 16, *c.Indices.Spans.Replicas)
 	assert.EqualValues(t, 300, c.Indices.Spans.Priority)
 	assert.EqualValues(t, 301, c.Indices.Services.Priority)
 	assert.EqualValues(t, 302, c.Indices.Dependencies.Priority)
diff --git a/cmd/jaeger/internal/extension/jaegerstorage/config_test.go b/cmd/jaeger/internal/extension/jaegerstorage/config_test.go
index c8e02148..befb5318 100644
--- a/cmd/jaeger/internal/extension/jaegerstorage/config_test.go
+++ b/cmd/jaeger/internal/extension/jaegerstorage/config_test.go
@@ -118,3 +118,14 @@ metric_backends:
 	require.NoError(t, conf.Unmarshal(cfg))
 	assert.NotEmpty(t, cfg.MetricBackends["some_metrics_storage"].Prometheus.ServerURL)
 }
+
+func TestConfigDefaultElasticsearchAsMetricsBackend(t *testing.T) {
+	conf := loadConf(t, `
+metric_backends:
+  some_metrics_storage:
+    elasticsearch:
+`)
+	cfg := createDefaultConfig().(*Config)
+	require.NoError(t, conf.Unmarshal(cfg))
+	assert.NotEmpty(t, cfg.MetricBackends["some_metrics_storage"].Elasticsearch.Servers)
+}
diff --git a/cmd/jaeger/internal/extension/jaegerstorage/extension_test.go b/cmd/jaeger/internal/extension/jaegerstorage/extension_test.go
index 53c9f01e..6cd689b5 100644
--- a/cmd/jaeger/internal/extension/jaegerstorage/extension_test.go
+++ b/cmd/jaeger/internal/extension/jaegerstorage/extension_test.go
@@ -300,6 +300,22 @@ func TestPrometheus(t *testing.T) {
 	require.NoError(t, ext.Shutdown(ctx))
 }
 
+func TestElasticsearchAsMetricsBackend(t *testing.T) {
+	ext := makeStorageExtension(t, &Config{
+		MetricBackends: map[string]MetricBackend{
+			"foo": {
+				Elasticsearch: &esCfg.Configuration{
+					Servers: []string{"localhost:9200"},
+				},
+			},
+		},
+	})
+	ctx := context.Background()
+	err := ext.Start(ctx, componenttest.NewNopHost())
+	require.NoError(t, err)
+	require.NoError(t, ext.Shutdown(ctx))
+}
+
 func TestStartError(t *testing.T) {
 	ext := makeStorageExtension(t, &Config{
 		TraceBackends: map[string]TraceBackend{
diff --git a/internal/storage/elasticsearch/config/config_test.go b/internal/storage/elasticsearch/config/config_test.go
index f6df53c4..72af30e6 100644
--- a/internal/storage/elasticsearch/config/config_test.go
+++ b/internal/storage/elasticsearch/config/config_test.go
@@ -75,6 +75,10 @@ func copyToTempFile(t *testing.T, pattern string, filename string) (file *os.Fil
 	return tempFile
 }
 
+func int64Ptr(v int64) *int64 {
+	return &v
+}
+
 func TestNewClient(t *testing.T) {
 	const (
 		pwd1       = "password"
@@ -445,17 +449,17 @@ func TestApplyDefaults(t *testing.T) {
 			IndexPrefix: "hello",
 			Spans: IndexOptions{
 				Shards:   5,
-				Replicas: 1,
+				Replicas: int64Ptr(1),
 				Priority: 10,
 			},
 			Services: IndexOptions{
 				Shards:   5,
-				Replicas: 1,
+				Replicas: int64Ptr(1),
 				Priority: 20,
 			},
 			Dependencies: IndexOptions{
 				Shards:   5,
-				Replicas: 1,
+				Replicas: int64Ptr(1),
 				Priority: 30,
 			},
 			Sampling: IndexOptions{},
@@ -528,17 +532,17 @@ func TestApplyDefaults(t *testing.T) {
 					IndexPrefix: "hello",
 					Spans: IndexOptions{
 						Shards:   5,
-						Replicas: 1,
+						Replicas: int64Ptr(1),
 						Priority: 10,
 					},
 					Services: IndexOptions{
 						Shards:   5,
-						Replicas: 1,
+						Replicas: int64Ptr(1),
 						Priority: 20,
 					},
 					Dependencies: IndexOptions{
 						Shards:   5,
-						Replicas: 1,
+						Replicas: int64Ptr(1),
 						Priority: 30,
 					},
 				},
@@ -574,17 +578,17 @@ func TestApplyDefaults(t *testing.T) {
 					IndexPrefix: "hello",
 					Spans: IndexOptions{
 						Shards:   5,
-						Replicas: 1,
+						Replicas: int64Ptr(1),
 						Priority: 10,
 					},
 					Services: IndexOptions{
 						Shards:   5,
-						Replicas: 1,
+						Replicas: int64Ptr(1),
 						Priority: 20,
 					},
 					Dependencies: IndexOptions{
 						Shards:   5,
-						Replicas: 1,
+						Replicas: int64Ptr(1),
 						Priority: 30,
 					},
 				},
diff --git a/internal/storage/metricstore/elasticsearch/factory_test.go b/internal/storage/metricstore/elasticsearch/factory_test.go
new file mode 100644
index 00000000..bd734697
--- /dev/null
+++ b/internal/storage/metricstore/elasticsearch/factory_test.go
@@ -0,0 +1,73 @@
+// Copyright (c) 2025 The Jaeger Authors.
+// SPDX-License-Identifier: Apache-2.0
+
+package elasticsearch
+
+import (
+	"net"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+
+	"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/config"
+	"github.com/jaegertracing/jaeger/internal/storage/v1"
+	"github.com/jaegertracing/jaeger/internal/telemetry"
+)
+
+var _ storage.MetricStoreFactory = new(Factory)
+
+func TestCreateMetricsReader(t *testing.T) {
+	listener, err := net.Listen("tcp", "localhost:")
+	require.NoError(t, err)
+	assert.NotNil(t, listener)
+	defer listener.Close()
+
+	cfg := config.Configuration{
+		Servers: []string{"http://" + listener.Addr().String()},
+	}
+	f, err := NewFactory(cfg, telemetry.NoopSettings())
+	require.NoError(t, err)
+	require.NotNil(t, f)
+
+	reader, err := f.CreateMetricsReader()
+
+	require.NoError(t, err)
+	assert.NotNil(t, reader)
+}
+
+func TestNewFactory(t *testing.T) {
+	tests := []struct {
+		name        string
+		cfg         config.Configuration
+		expectedErr bool
+	}{
+		{
+			name: "valid config",
+			cfg: config.Configuration{
+				Servers: []string{"http://localhost:9200"},
+			},
+			expectedErr: false,
+		},
+		{
+			name: "invalid config - no servers",
+			cfg: config.Configuration{
+				Servers: []string{},
+			},
+			expectedErr: true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			telset := telemetry.NoopSettings()
+			_, err := NewFactory(tt.cfg, telset)
+
+			if tt.expectedErr {
+				require.Error(t, err)
+			} else {
+				require.NoError(t, err)
+			}
+		})
+	}
+}
diff --git a/internal/storage/metricstore/elasticsearch/package_test.go b/internal/storage/metricstore/elasticsearch/package_test.go
new file mode 100644
index 00000000..071efbba
--- /dev/null
+++ b/internal/storage/metricstore/elasticsearch/package_test.go
@@ -0,0 +1,14 @@
+// Copyright (c) 2025 The Jaeger Authors.
+// SPDX-License-Identifier: Apache-2.0
+
+package elasticsearch
+
+import (
+	"testing"
+
+	"github.com/jaegertracing/jaeger/internal/testutils"
+)
+
+func TestMain(m *testing.M) {
+	testutils.VerifyGoLeaks(m)
+}
diff --git a/internal/storage/metricstore/elasticsearch/reader_test.go b/internal/storage/metricstore/elasticsearch/reader_test.go
new file mode 100644
index 00000000..4ccb8588
--- /dev/null
+++ b/internal/storage/metricstore/elasticsearch/reader_test.go
@@ -0,0 +1,125 @@
+// Copyright (c) 2025 The Jaeger Authors.
+// SPDX-License-Identifier: Apache-2.0
+
+package elasticsearch
+
+import (
+	"context"
+	"net"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+	sdktrace "go.opentelemetry.io/otel/sdk/trace"
+	"go.opentelemetry.io/otel/sdk/trace/tracetest"
+	"go.opentelemetry.io/otel/trace"
+	"go.uber.org/zap"
+
+	"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/config"
+	"github.com/jaegertracing/jaeger/internal/storage/v1/api/metricstore"
+)
+
+func tracerProvider(t *testing.T) (trace.TracerProvider, *tracetest.InMemoryExporter, func()) {
+	exporter := tracetest.NewInMemoryExporter()
+	tp := sdktrace.NewTracerProvider(
+		sdktrace.WithSampler(sdktrace.AlwaysSample()),
+		sdktrace.WithSyncer(exporter),
+	)
+	closer := func() {
+		require.NoError(t, tp.Shutdown(context.Background()))
+	}
+	return tp, exporter, closer
+}
+
+func TestGetLatencies(t *testing.T) {
+	logger := zap.NewNop()
+	tracer, _, closer := tracerProvider(t)
+	defer closer()
+
+	listener, err := net.Listen("tcp", "localhost:")
+	require.NoError(t, err)
+	assert.NotNil(t, listener)
+	defer listener.Close()
+
+	reader, err := NewMetricsReader(config.Configuration{
+		Servers: []string{"http://" + listener.Addr().String()},
+	}, logger, tracer)
+	require.NoError(t, err)
+	require.NotNil(t, reader)
+
+	qParams := &metricstore.LatenciesQueryParameters{}
+	r, err := reader.GetLatencies(context.Background(), qParams)
+	assert.Zero(t, r)
+	require.ErrorIs(t, err, ErrNotImplemented)
+	require.EqualError(t, err, ErrNotImplemented.Error())
+}
+
+func TestGetCallRates(t *testing.T) {
+	logger := zap.NewNop()
+	tracer, _, closer := tracerProvider(t)
+	defer closer()
+	listener, err := net.Listen("tcp", "localhost:")
+	require.NoError(t, err)
+	assert.NotNil(t, listener)
+	defer listener.Close()
+
+	reader, err := NewMetricsReader(config.Configuration{
+		Servers: []string{"http://" + listener.Addr().String()},
+	}, logger, tracer)
+
+	require.NoError(t, err)
+	require.NotNil(t, reader)
+
+	qParams := &metricstore.CallRateQueryParameters{}
+	r, err := reader.GetCallRates(context.Background(), qParams)
+	assert.Zero(t, r)
+	require.ErrorIs(t, err, ErrNotImplemented)
+	require.EqualError(t, err, ErrNotImplemented.Error())
+}
+
+func TestGetErrorRates(t *testing.T) {
+	logger := zap.NewNop()
+	tracer, _, closer := tracerProvider(t)
+	defer closer()
+
+	listener, err := net.Listen("tcp", "localhost:")
+	require.NoError(t, err)
+	assert.NotNil(t, listener)
+	defer listener.Close()
+
+	reader, err := NewMetricsReader(config.Configuration{
+		Servers: []string{"http://" + listener.Addr().String()},
+	}, logger, tracer)
+
+	require.NoError(t, err)
+	require.NotNil(t, reader)
+
+	qParams := &metricstore.ErrorRateQueryParameters{}
+	r, err := reader.GetErrorRates(context.Background(), qParams)
+	assert.Zero(t, r)
+	require.ErrorIs(t, err, ErrNotImplemented)
+	require.EqualError(t, err, ErrNotImplemented.Error())
+}
+
+func TestGetMinStepDuration(t *testing.T) {
+	params := metricstore.MinStepDurationQueryParameters{}
+	logger := zap.NewNop()
+	tracer, _, closer := tracerProvider(t)
+	defer closer()
+
+	listener, err := net.Listen("tcp", "localhost:")
+	require.NoError(t, err)
+	assert.NotNil(t, listener)
+	defer listener.Close()
+
+	reader, err := NewMetricsReader(config.Configuration{
+		Servers: []string{"http://" + listener.Addr().String()},
+	}, logger, tracer)
+	require.NoError(t, err)
+	require.NotNil(t, reader)
+
+	minStep, err := reader.GetMinStepDuration(context.Background(), &params)
+	require.NoError(t, err)
+	assert.Equal(t, time.Millisecond, minStep)
+}
diff --git a/internal/storage/metricstore/factory_test.go b/internal/storage/metricstore/factory_test.go
index 350c0e38..8a62f61a 100644
--- a/internal/storage/metricstore/factory_test.go
+++ b/internal/storage/metricstore/factory_test.go
@@ -18,7 +18,7 @@ import (
 	"github.com/jaegertracing/jaeger/internal/telemetry"
 )
 
-var _ storage.MetricStoreFactory = new(Factory)
+var _ storage.V1MetricStoreFactory = new(Factory)
 
 func withConfig(storageType string) FactoryConfig {
 	return FactoryConfig{
@@ -69,7 +69,7 @@ func TestCreateMetricsReader(t *testing.T) {
 }
 
 type configurable struct {
-	mocks.MetricStoreFactory
+	mocks.V1MetricStoreFactory
 	flagSet *flag.FlagSet
 	viper   *viper.Viper
 	logger  *zap.Logger
diff --git a/internal/storage/v1/elasticsearch/factory_test.go b/internal/storage/v1/elasticsearch/factory_test.go
index 625bbd4d..b032a04d 100644
--- a/internal/storage/v1/elasticsearch/factory_test.go
+++ b/internal/storage/v1/elasticsearch/factory_test.go
@@ -214,7 +214,19 @@ func TestCreateTemplates(t *testing.T) {
 		}
 		f.logger = zaptest.NewLogger(t)
 		f.metricsFactory = metrics.NullFactory
-		f.config = &escfg.Configuration{CreateIndexTemplates: true, Indices: escfg.Indices{IndexPrefix: test.indexPrefix}}
+		f.config = &escfg.Configuration{CreateIndexTemplates: true, Indices: escfg.Indices{
+			IndexPrefix: test.indexPrefix,
+			Spans: escfg.IndexOptions{
+				Shards:   3,
+				Replicas: ptr(int64(1)),
+				Priority: 10,
+			},
+			Services: escfg.IndexOptions{
+				Shards:   3,
+				Replicas: ptr(int64(1)),
+				Priority: 10,
+			},
+		}}
 		f.tracer = otel.GetTracerProvider()
 		client, err := f.newClientFn(&escfg.Configuration{}, zaptest.NewLogger(t), metrics.NullFactory)
 		require.NoError(t, err)
diff --git a/internal/storage/v1/elasticsearch/mappings/command_test.go b/internal/storage/v1/elasticsearch/mappings/command_test.go
index 970e6073..c232ec64 100644
--- a/internal/storage/v1/elasticsearch/mappings/command_test.go
+++ b/internal/storage/v1/elasticsearch/mappings/command_test.go
@@ -85,19 +85,19 @@ func Test_getMappingAsString(t *testing.T) {
 		wantErr error
 	}{
 		{
-			name: "ES version 7", args: Options{Mapping: "jaeger-span", EsVersion: 7, Shards: 5, Replicas: 1, IndexPrefix: "test", UseILM: "true", ILMPolicyName: "jaeger-test-policy"},
+			name: "ES version 7", args: Options{Mapping: "jaeger-span", EsVersion: 7, Shards: 5, Replicas: ptr(int64(1)), IndexPrefix: "test", UseILM: "true", ILMPolicyName: "jaeger-test-policy"},
 			want: "ES version 7",
 		},
 		{
-			name: "Parse Error version 7", args: Options{Mapping: "jaeger-span", EsVersion: 7, Shards: 5, Replicas: 1, IndexPrefix: "test", UseILM: "true", ILMPolicyName: "jaeger-test-policy"},
+			name: "Parse Error version 7", args: Options{Mapping: "jaeger-span", EsVersion: 7, Shards: 5, Replicas: ptr(int64(1)), IndexPrefix: "test", UseILM: "true", ILMPolicyName: "jaeger-test-policy"},
 			wantErr: errors.New("parse error"),
 		},
 		{
-			name: "Parse bool error", args: Options{Mapping: "jaeger-span", EsVersion: 7, Shards: 5, Replicas: 1, IndexPrefix: "test", UseILM: "foo", ILMPolicyName: "jaeger-test-policy"},
+			name: "Parse bool error", args: Options{Mapping: "jaeger-span", EsVersion: 7, Shards: 5, Replicas: ptr(int64(1)), IndexPrefix: "test", UseILM: "foo", ILMPolicyName: "jaeger-test-policy"},
 			wantErr: errors.New("strconv.ParseBool: parsing \"foo\": invalid syntax"),
 		},
 		{
-			name: "Invalid Mapping type", args: Options{Mapping: "invalid-mapping", EsVersion: 7, Shards: 5, Replicas: 1, IndexPrefix: "test", UseILM: "true", ILMPolicyName: "jaeger-test-policy"},
+			name: "Invalid Mapping type", args: Options{Mapping: "invalid-mapping", EsVersion: 7, Shards: 5, Replicas: ptr(int64(1)), IndexPrefix: "test", UseILM: "true", ILMPolicyName: "jaeger-test-policy"},
 			wantErr: errors.New("invalid mapping type: invalid-mapping"),
 		},
 	}
@@ -148,7 +148,7 @@ func TestGenerateMappings(t *testing.T) {
 				Mapping:       "jaeger-span",
 				EsVersion:     7,
 				Shards:        5,
-				Replicas:      1,
+				Replicas:      ptr(int64(1)),
 				IndexPrefix:   "jaeger-index",
 				UseILM:        "false",
 				ILMPolicyName: "jaeger-ilm-policy",
@@ -161,7 +161,7 @@ func TestGenerateMappings(t *testing.T) {
 				Mapping:       "jaeger-service",
 				EsVersion:     7,
 				Shards:        5,
-				Replicas:      1,
+				Replicas:      ptr(int64(1)),
 				IndexPrefix:   "jaeger-service-index",
 				UseILM:        "true",
 				ILMPolicyName: "service-ilm-policy",
diff --git a/internal/storage/v1/elasticsearch/mappings/flags_test.go b/internal/storage/v1/elasticsearch/mappings/flags_test.go
index ebc957d3..32b2dd4d 100644
--- a/internal/storage/v1/elasticsearch/mappings/flags_test.go
+++ b/internal/storage/v1/elasticsearch/mappings/flags_test.go
@@ -19,7 +19,7 @@ func TestOptionsWithDefaultFlags(t *testing.T) {
 	assert.Empty(t, o.Mapping)
 	assert.Equal(t, uint(7), o.EsVersion)
 	assert.EqualValues(t, 5, o.Shards)
-	assert.EqualValues(t, 1, o.Replicas)
+	assert.EqualValues(t, 1, *o.Replicas)
 
 	assert.Empty(t, o.IndexPrefix)
 	assert.Equal(t, "false", o.UseILM)
@@ -44,7 +44,7 @@ func TestOptionsWithFlags(t *testing.T) {
 	assert.Equal(t, "jaeger-span", o.Mapping)
 	assert.Equal(t, uint(7), o.EsVersion)
 	assert.Equal(t, int64(5), o.Shards)
-	assert.Equal(t, int64(1), o.Replicas)
+	assert.Equal(t, int64(1), *o.Replicas)
 	assert.Equal(t, "test", o.IndexPrefix)
 	assert.Equal(t, "true", o.UseILM)
 	assert.Equal(t, "jaeger-test-policy", o.ILMPolicyName)
diff --git a/internal/storage/v1/elasticsearch/mappings/mapping_test.go b/internal/storage/v1/elasticsearch/mappings/mapping_test.go
index daa9f6e4..fbb90aa5 100644
--- a/internal/storage/v1/elasticsearch/mappings/mapping_test.go
+++ b/internal/storage/v1/elasticsearch/mappings/mapping_test.go
@@ -25,6 +25,10 @@ import (
 //go:embed fixtures/*.json
 var FIXTURES embed.FS
 
+func ptr[T any](v T) *T {
+	return &v
+}
+
 func TestMappingBuilderGetMapping(t *testing.T) {
 	tests := []struct {
 		mapping   MappingType
@@ -47,7 +51,7 @@ func TestMappingBuilderGetMapping(t *testing.T) {
 			defaultOpts := func(p int64) config.IndexOptions {
 				return config.IndexOptions{
 					Shards:   3,
-					Replicas: 3,
+					Replicas: ptr(int64(3)),
 					Priority: p,
 				}
 			}
@@ -176,7 +180,7 @@ func TestMappingBuilderFixMapping(t *testing.T) {
 		t.Run(test.name, func(t *testing.T) {
 			indexTemOps := config.IndexOptions{
 				Shards:   3,
-				Replicas: 5,
+				Replicas: ptr(int64(5)),
 				Priority: 500,
 			}
 			mappingBuilder := MappingBuilder{
@@ -324,7 +328,7 @@ func TestMappingBuilderGetSpanServiceMappings(t *testing.T) {
 		t.Run(test.name, func(t *testing.T) {
 			indexTemOps := config.IndexOptions{
 				Shards:   3,
-				Replicas: 3,
+				Replicas: ptr(int64(3)),
 			}
 
 			mappingBuilder := MappingBuilder{
@@ -357,6 +361,13 @@ func TestMappingBuilderGetDependenciesMappings(t *testing.T) {
 
 	mappingBuilder := MappingBuilder{
 		TemplateBuilder: &tb,
+		Indices: config.Indices{
+			Dependencies: config.IndexOptions{
+				Replicas: ptr(int64(1)),
+				Shards:   3,
+				Priority: 10,
+			},
+		},
 	}
 	_, err := mappingBuilder.GetDependenciesMappings()
 	require.EqualError(t, err, "template load error")
@@ -370,6 +381,13 @@ func TestMappingBuilderGetSamplingMappings(t *testing.T) {
 
 	mappingBuilder := MappingBuilder{
 		TemplateBuilder: &tb,
+		Indices: config.Indices{
+			Sampling: config.IndexOptions{
+				Replicas: ptr(int64(1)),
+				Shards:   3,
+				Priority: 10,
+			},
+		},
 	}
 	_, err := mappingBuilder.GetSamplingMappings()
 	require.EqualError(t, err, "template load error")
diff --git a/internal/storage/v1/elasticsearch/options_test.go b/internal/storage/v1/elasticsearch/options_test.go
index ece7ab45..d7227de9 100644
--- a/internal/storage/v1/elasticsearch/options_test.go
+++ b/internal/storage/v1/elasticsearch/options_test.go
@@ -27,13 +27,18 @@ func TestOptions(t *testing.T) {
 	assert.EqualValues(t, 5, primary.Indices.Services.Shards)
 	assert.EqualValues(t, 5, primary.Indices.Sampling.Shards)
 	assert.EqualValues(t, 5, primary.Indices.Dependencies.Shards)
-	assert.EqualValues(t, 1, primary.Indices.Spans.Replicas)
-	assert.EqualValues(t, 1, primary.Indices.Services.Replicas)
-	assert.EqualValues(t, 1, primary.Indices.Sampling.Replicas)
-	assert.EqualValues(t, 1, primary.Indices.Dependencies.Replicas)
+	require.NotNil(t, primary.Indices.Spans.Replicas)
+	assert.EqualValues(t, 1, *primary.Indices.Spans.Replicas)
+	require.NotNil(t, primary.Indices.Services.Replicas)
+	assert.EqualValues(t, 1, *primary.Indices.Services.Replicas)
+	require.NotNil(t, primary.Indices.Sampling.Replicas)
+	assert.EqualValues(t, 1, *primary.Indices.Sampling.Replicas)
+	require.NotNil(t, primary.Indices.Dependencies.Replicas)
+	assert.EqualValues(t, 1, *primary.Indices.Dependencies.Replicas)
 	assert.Equal(t, 72*time.Hour, primary.MaxSpanAge)
 	assert.False(t, primary.Sniffing.Enabled)
 	assert.False(t, primary.Sniffing.UseHTTPS)
+	assert.False(t, primary.DisableHealthCheck)
 }
 
 func TestOptionsWithFlags(t *testing.T) {
@@ -47,6 +52,7 @@ func TestOptionsWithFlags(t *testing.T) {
 		"--es.password-file=/foo/bar/baz",
 		"--es.sniffer=true",
 		"--es.sniffer-tls-enabled=true",
+		"--es.disable-health-check=true",
 		"--es.max-span-age=48h",
 		"--es.num-shards=20",
 		"--es.num-replicas=10",
@@ -78,6 +84,7 @@ func TestOptionsWithFlags(t *testing.T) {
 	assert.Equal(t, 48*time.Hour, primary.MaxSpanAge)
 	assert.True(t, primary.Sniffing.Enabled)
 	assert.True(t, primary.Sniffing.UseHTTPS)
+	assert.True(t, primary.DisableHealthCheck)
 	assert.False(t, primary.TLS.Insecure)
 	assert.True(t, primary.TLS.InsecureSkipVerify)
 	assert.True(t, primary.Tags.AllAsFields)