#10508·dapr

mTLS metrics are never emitted by the sidecar

Author: MyMirelHubCreated Sep 17, 2026Updated Sep 17, 2026

In what area(s)?

/area runtime

What version of Dapr?

1.18.4 (also present on master at the time of writing)

Expected Behavior

The four mTLS metrics documented in docs/development/dapr-metrics.md are exported by the sidecar:

  • dapr_runtime_mtls_init_total
  • dapr_runtime_mtls_init_fail_total
  • dapr_runtime_mtls_workload_cert_rotated_total
  • dapr_runtime_mtls_workload_cert_rotated_fail_total

Actual Behavior

None of them are ever exported. On a healthy cluster with mTLS enabled and 7 app IDs exporting other runtime metrics normally, dapr_runtime_mtls_* matches zero series, and scraping a sidecar's :9090/metrics directly returns nothing for the prefix while dapr_runtime_component_* is present on the same endpoint.

Three of the four cannot be emitted at all:

Metric Recorder Call sites outside service_monitoring.go
mtls_init_total MTLSInitCompleted 1, but unreachable — see below
mtls_init_fail_total MTLSInitFailed 0
mtls_workload_cert_rotated_total MTLSWorkLoadCertRotationCompleted 0
mtls_workload_cert_rotated_fail_total MTLSWorkLoadCertRotationFailed 8, all in pkg/security/sentry.go

Why mtls_init_total never records

serviceMetrics is constructed with enabled: false, and every recorder is guarded by it:

go
func (s *serviceMetrics) MTLSInitCompleted() {
	if s.enabled {
		stats.RecordWithOptions(...)
	}
}

enabled is only set to true by serviceMetrics.Init, reached via diag.InitMetrics from runtime.FromConfig.

The single call site is in the security provider's run loop, pkg/security/security.go:

go
p.sec.id = id.ID
close(p.readyCh)
diagnostics.DefaultMonitoring.MTLSInitCompleted()
p.htarget.Ready()

In cmd/daprd/app/app.go that runner is started before the runtime is built, and the runtime is only built after the security handler is available:

go
err = concurrency.NewRunnerManager(
	secProvider.Run,                          // records MTLSInitCompleted
	func(ctx context.Context) error {
		sec, serr := secProvider.Handler(ctx) // blocks on readyCh
		...
		rt, rerr := runtime.FromConfig(ctx, ...)  // -> InitMetrics -> enabled = true

MTLSInitCompleted is emitted immediately after close(p.readyCh), whereas InitMetrics is only reached once Handler has returned and the full runtime config has been loaded. enabled is therefore still false when the record is attempted and the call is a silent no-op. It is a race in principle, but the two are far enough apart that it resolves the same way every time — no sidecar in the sample exported the metric.

mtls_workload_cert_rotated_fail_total is reachable, but only on an actual rotation failure, and the first attempt happens in the same pre-InitMetrics window.

Steps to Reproduce the Problem

On a Kubernetes cluster with Dapr installed and mTLS enabled:

bash
kubectl exec -n <ns> <any-dapr-pod> -c daprd -- wget -qO- localhost:9090/metrics | grep dapr_runtime_mtls

Returns nothing. grep dapr_runtime_component on the same endpoint returns series, confirming the exporter is working.

Release Note

RELEASE NOTE: FIX mTLS metrics are never emitted by the sidecar.