In modern distributed systems, TLS certificates are the glue that holds everything together while keeping it safe. Certificates aren't only used for encrypting user traffic; they are fundamental building blocks of trust for your entire system.
Indeed, an expired certificate is not just a minor technical glitch. It is a direct hit on your most critical systems:
-
Your CI/CD pipeline grinds to a halt because it can not trust the internal image registry.
-
Your Single Sign-On (SSO) system fails, locking all your internal users out.
-
Your external clients see scary browser warnings, shattering user trust and forcing support tickets.
-
Your SLOs burn due to services not being able to communicate with one another.
In Kubernetes, certificates are usually dynamically generated and auto-renewed by tools like
Additionally, only monitoring the certificates for external Load Balancers might lead to huge internal risks, since many certificates never get exposed to external users.
In this blog post, we will guide you through establishing comprehensive, cluster-wide certificate monitoring using the OpenTelemetry Collector, the x509-certificate-exporter, and Elastic Observability.
Classical approach: HTTP monitoring
The classical approach to monitor TLS certificate expiration in the Elastic Observability is by treating it like any other service availability check. Historically, this was accomplished using Heartbeat or, more recently, Elastic Observability's Synthetics. These tools perform an external check against a public HTTPS endpoint and automatically extract the certificate's validity dates, allowing you to configure a Synthetics TLS certificate rule in Kibana to trigger an alert when expiration is within a specified threshold (e.g., 30 days).
While effective for external-facing services, this "classical" approach has two major shortcomings when dealing with Kubernetes:
-
It only works for certificates exposed via HTTP(S), meaning you cannot use this for internal services, databases, or message queues using other protocols. In other words, this won't work to monitor common, critical TLS certificates such as Kafka's.
-
The monitoring agent must have network access to the endpoint. In a segmented or private Kubernetes environment, deploying agents with the necessary access often introduces unnecessary complexity or security risks.
To gain true cluster-wide visibility, we need to inspect the certificates at their source: inside Kubernetes Secrets or ConfigMaps.
A Kubernetes-native approach: monitor Secrets and ConfigMaps
Monitoring TLS certificate expiration directly within Kubernetes Secrets and ConfigMaps is the only reliable way to gain visibility into internal, non-HTTP-exposed certificates, such as those used for service meshes, internal registries, or databases. In this section, we will use the OpenTelemetry Collector to monitor certificate expiration.
The OpenTelemetry Collector provides a mechanism to read up-to-date information from the Kubernetes API, including Secrets, via the k8sobjects receiver. However, this receiver only fetches raw TLS certificate resource data, which the OpenTelemetry Transformation Language (OTTL) can not properly parse. Therefore, we need to use a dedicated exporter to collect the certificate data and expose the results in a digestible format.
The industry-standard solution
As mentioned above, simply reading certificate information from the Kubernetes API is not a feasible solution. We will therefore use a specialized, lightweight exporter (specifically, the popular x509-certificate-exporter) to collect TLS certificate data and expose the results, allowing the OpenTelemetry Collector's Prometheus receiver to seamlessly scrape the data and send it to Elastic Observability. This approach immediately and easily enables us to monitor both certificates generated by
A fully working configuration example and a script to set up a complete local development environment is available here. Feel free to use it to follow along as you read through this guide and try out the examples. Please note that, while this repository uses the Elastic Distribution of OpenTelemetry (EDOT), it can be easily adapted to use the OpenTelemetry Collector.
Helm Chart Configuration
We configured the
secretsExporter:
secretTypes:
- type: kubernetes.io/tls
key: tls.crt
# For ECK that uses different secret types
- type: Opaque
key: tls.crt
- type: Opaque
key: ca.crt
configMapKeys:
- tls.crt
- ca.crt
# Create a service to have a stable endpoint for scraping metrics
service:
create: true
# -- TCP port to expose the Service on
port: 9793
# Disable prometheus service monitor and prometheus rules
prometheusServiceMonitor:
create: false
prometheusRules:
create: false
We refer to the reference values.yaml to get insights in the plethora of configuration options.
OpenTelemetry Collector Configuration
Afterward, we configured the OpenTelemetry Collector to scrape the metrics from the service:
prometheus/cert-expiration:
config:
scrape_configs:
- job_name: "cert-expiration"
scrape_interval: 60m
static_configs:
- targets:
- "x509-certificate-exporter.monitoring.svc.cluster.local:9793"
We deliberately used a long scrape interval of 60 minutes, because certificate expiration is a low-frequency concern.
Visualizing the data in Kibana
Once the data is ingested, we can explore it using Discover. We can select the
An example document looks like the following:
{
"@timestamp": "2025-12-19T09:43:45.317Z",
"_metric_names_hash": "7d113f55b70019d9",
"attributes": {
"issuer_CN": "tls-cert.example.com",
"issuer_O": "TLS Cert",
"secret_key": "tls.crt",
"secret_name": "tls-cert-secret",
"secret_namespace": "test-certs",
"serial_number": "250887723804527203192865532237673843132727735771",
"subject_CN": "tls-cert.example.com",
"subject_O": "TLS Cert"
},
"data_stream": {
"dataset": "prometheusreceiver.otel",
"namespace": "default",
"type": "metrics"
},
"metrics": {
"x509_cert_expired": 0,
"x509_cert_not_after": 1768488242,
"x509_cert_not_before": 1765896242
},
"resource": {
"attributes": {
"server.address": "x509-certificate-exporter.monitoring.svc.cluster.local",
"server.port": "9793",
"service.instance.id": "x509-certificate-exporter.monitoring.svc.cluster.local:9793",
"service.name": "cert-expiration",
"url.scheme": "http"
}
},
"scope": {
"name": "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/prometheusreceiver",
"version": "9.2.2"
}
}
The core metric reported by the
- secret_namespace: The namespace of the Secret containing the certificate.
- secret_name: The name of the Secret containing the certificate.
- secret_key: The specific key within the Secret where the certificate is stored.
In the case of
Finally, we can leverage ES|QL to compute the remaining days until expiration.
In the following examples, we will use the
For
TS metrics-*
| WHERE metrics.x509_cert_not_after is not NULL
| STATS expiration_date = MAX(LAST_OVER_TIME(metrics.x509_cert_not_after)) by attributes.secret_namespace, attributes.secret_name, attributes.secret_key
| EVAL remaining_days = DATE_DIFF("days", NOW(), TO_DATETIME (1000 * expiration_date))
| EVAL expiration_date = TO_DATETIME(1000 * expiration_date)
| SORT expiration_date ASC
And for
TS metrics-*
| WHERE metrics.x509_cert_not_after IS NOT NULL
| WHERE attributes.filepath IS NOT NULL
| DISSECT attributes.filepath "k8s/%{namespace}/%{configmap}"
| WHERE configmap != "kube-root-ca.crt" // Filter out the Kubernetes API server certificate's signing CA
| STATS expiration_date = MAX(LAST_OVER_TIME(metrics.x509_cert_not_after)) by namespace, configmap, filename
| EVAL remaining_days = DATE_DIFF("days", NOW(), TO_DATETIME (1000 * expiration_date))
| EVAL expiration_date = TO_DATETIME(1000 * expiration_date)
| SORT expiration_date ASC
Based on these core queries, we can easily build a dashboard that shows the remaining days until expiration for all the certificates in the cluster:
and create alerts about certificates that are about to expire by adding a condition after the query:
WHERE remaining_days < 30
Conclusion
In this blog post, we explored how to monitor TLS certificate expiration within a Kubernetes cluster using the OpenTelemetry Collector. We discussed the limitations of traditional HTTP-based monitoring approaches and introduced a Kubernetes-native solution leveraging the
For the sake of simplicity, we just focused on monitoring certificate expiration with the OpenTelemetry Collector on Kubernetes. However, this approach can be easily applied with classical Elastic Agent by leveraging the Prometheus input package (read more on how to use input packages here) and can be also extended to monitor certificates on virtual machines or bare-metal servers by deploying the
Finally, is worth knowing that Elastic Observability, offers an officially supported distribution of the OpenTelemetry Collector, called Elastic Distributions of OpenTelemetry (EDOT).
If you are an Elastic user, you could consider using EDOT Collector to monitor certificates with OpenTelemetry: since it is supported by Elastic Observability, it will be easier to manage and keep up to date. Alternatively you can use upstream OTel compnents also.
What's next?
Now that Elastic supports Rule Templates and OpenTelemetry content packs, our near-term objective is to contribute to the integration repository to make the setup of certificate monitoring even easier for our users. Stay tuned for more updates on this!
Check out other resources on Elastic's OpenTelemetry
Elastic's EDOT PHP Contribution
Opentelemetry SDK Central Management with EDOT
Also sign up for Elastic Cloud and try out your application with OpenTelemetry in Elastic
