# x509-certificate-exporter: certificate expiry and CRL freshness as Prometheus series

> The x509-certificate-exporter watches TLS certificates held in Kubernetes Secrets, ConfigMaps, kubeconfigs and node PKI directories and publishes expiration and revocation data as Prometheus series, with v4 rewritten around a YAML config file.

**enix/x509-certificate-exporter** — A Prometheus exporter for X.509 certificates, built for Kubernetes first but equally happy as a standalone binary

- Repository: https://github.com/enix/x509-certificate-exporter
- Stars: 962 · Forks: 112
- Language: Go
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/enix-x509-certificate-exporter

## Turning expiring certificates into Prometheus series

Certificates expire in places nobody is watching: a TLS Secret in a namespace nobody looks at, a ConfigMap holding a cert chain, a kubeconfig on a laptop, a kubelet serving directory on a node. The x509-certificate-exporter takes those certificates as they already exist in a cluster and turns expirations into Prometheus series, so alerting stays in the stack you already run. The project description is explicit about the priority order: it is built for Kubernetes first, yet equally happy as a standalone binary. The watched surface is wide. TLS Secrets of any type are covered, including `kubernetes.io/tls`, Opaque bundles and full chains, across all namespaces or a curated subset, and ConfigMaps are read for cert or CRL data under any key the config points at. Version 4 is a full rewrite around a YAML config file and a pluggable architecture, which is the version this article describes.

## LIST plus WATCH instead of polling the API server

The data flow matters for anyone running this against a large cluster. On Kubernetes the exporter pages through Secrets and ConfigMaps with a paginated LIST plus WATCH loop, 50 objects per page by default, rather than polling `kube-apiserver` on a timer. The stated consequence is that watch traffic scales with churn in the cluster, not with the rate at which Prometheus scrapes. Parsed certificates are cached, so repeated scrapes do not re-decode the same PEM blocks. The release notes for v4 also claim a memory-safe Kubernetes watch with flat RAM instead of spikes, and cite memory limits dropping by roughly a factor of ten on Secret-heavy clusters. The implementation is plain Go, described as a few thousand lines with no CGO and no plugins, which is what makes an afternoon-long audit plausible.

## Every certificate in a PEM bundle becomes its own series

A bundle is not treated as one opaque object. Every certificate in a multi-cert PEM chain becomes its own series, so intermediate CAs and trust roots show up alongside leaf certificates with no extra configuration on the operator's side. Kubeconfigs are handled the same way, whether the certificate is embedded as base64 or referenced as a PEM file, and every `cluster` and `user` block is exposed as its own series. Raw DER blobs are first-class inputs too, which matters because that is the format served by HTTP CRL Distribution Points, and the config marks those with `format: der`. The cost of this design is cardinality. Each certificate, each chain element and each kubeconfig entry becomes a separate time series carrying its own labels, and the project points at cardinality control as a dedicated topic in its FAQ documentation rather than treating it as an afterthought.

## CRL freshness tracked beside certificate expiry

Revocation lists get their own metric family, `x509_crl_*`, parsed into a separate namespace of series rather than folded into the certificate data. Two input shapes are supported. PEM `X509 CRL` blocks are parsed and may be intermixed freely with `CERTIFICATE` blocks in the same data, and raw DER `*.crl` files are handled with `format: der`. The reason to watch a CRL is stated in practical terms: a stale revocation list pages on-call before its consumers start rejecting certificates from that issuer. That is a failure mode certificate expiry monitoring alone does not cover, since a perfectly valid certificate can be revoked by a list nobody refreshed. Cluster-scoped trust configuration is also picked up, with inline `caBundle` PEM fields read from `MutatingWebhookConfiguration`, `ValidatingWebhookConfiguration`, the API-aggregation layer through `APIService`, and `CustomResourceDefinition` objects that carry a conversion webhook.

## Reading node PKI from disk with globs and symlink swaps

Not every certificate lives in the API server, so the exporter also reads the filesystem. Paths are selected with glob patterns, with single-star, double-star and question-mark wildcards all supported, and the watcher is designed for the way certificates actually rotate on disk: atomic symlink swaps are detected on the next poll, which covers certbot renewals and the `..data` directory pattern used by kubelet projected volumes. Deployment is a deliberate choice between two shapes. The exporter can run inside the exporter pod, or it can run as a node-local DaemonSet for cluster PKI, with kubelet, etcd and kube-apiserver named as the typical targets. The same flexibility applies outside Kubernetes, since each release ships statically-linked binaries for Linux, macOS, Windows, FreeBSD, OpenBSD, NetBSD, Illumos and Solaris across `amd64`, `arm64`, `armv7` and `riscv64`, with no daemon dependencies and file reads straight from disk.

## The v3 to v4 upgrade moves the chart to OCI and drops Alpine

Upgrading is where this project puts friction on purpose. The documentation carries an explicit warning for anyone coming from version 3: chart distribution moved to OCI on `quay.io`, the Alpine image variant is retired, and a number of values keys changed shape. There is a dedicated migration guide at `docs/migration-v3-to-v4.md` for that path. The rewrite also replaced configuration handling, so a YAML config file now sits at the centre of the pluggable architecture, and the upstream `go.mod` reflects a module path that carries the major version, which is the clearest signal that the import paths changed. The Go requirement moved as well, with the module declaring `go 1.26.0` and a `toolchain go1.27.2` directive. Anyone automating deployments against version 3 should read the migration guide before the chart pull fails on a moved registry path.

## Multi-cluster fan-in and per-source health signals

Two v4 features change how a platform team runs this at scale. The first is multi-cluster support from a single instance, where metrics from any number of clusters are fanned in using distinct kubeconfigs. The second is per-source observability: instead of one global error counter, each watched source carries granular health and triage signals, which is what you need when a single stale source is buried under thousands of healthy ones. A related change lifts watched resource labels onto the emitted certificate series, so a certificate inherits the labels of the workload it belongs to and can be sliced by application without a join in PromQL. The metrics documentation lists per-cert, per-source and health series, the label schema and PromQL examples, and the FAQ documentation covers memory sizing, cardinality control, high availability and non-Kubernetes use.

## Installing: the Helm chart first, the binary second

The project points you at the chart directory `./chart`, published as an OCI artifact, rather than giving a copy-and-paste command. That chart covers Deployments, DaemonSets, RBAC, a ServiceMonitor, a PrometheusRule and a Grafana dashboard, so a working alert path depends on those pieces existing in your cluster. The binary route is the alternative: pick the statically-linked build for your platform and architecture, drop it on the machine, and run it under systemd or Windows Services. No install command appears in the project documentation, so none is invented here. For source builds, the module identity and toolchain requirement are declared like this:

```go
module github.com/enix/x509-certificate-exporter/v4

go 1.26.0
```

Two documentation pages matter for a first deployment. `docs/metrics.md` gives the series and label schema with PromQL examples, and `docs/hardening.md` covers supply-chain verification, SBOM queries and immutable-digest pinning. The release artifacts are described as cosign-signed, backed by SLSA Level 3 provenance and SBOM attestations, which is what the hardening page expects you to check.

## Conclusion

Adopt enix/x509-certificate-exporter if your platform team already runs Prometheus and needs expiry plus CRL freshness alerts across cluster and node PKI without writing a custom scanner. Skip it if you have no compatible collector, or if you cannot absorb a v3 to v4 migration that moves the chart to OCI on quay.io and retires the Alpine image variant. Verify first how your PKCS#12 passphrases are sourced, because tryEmptyPassphrase means an empty passphrase is a supported configuration rather than an error.

## FAQ

### How does x509-certificate-exporter handle certificates inside a chain?

Every certificate in a multi-cert PEM bundle becomes its own series, so intermediate CAs and trust roots appear alongside leaf certificates without extra configuration. Kubeconfig `cluster` and `user` blocks are exposed the same way.

### Where does the exporter get PKCS#12 keystore passphrases?

From a sibling key in the same Secret, an external file, a cross-namespace Secret reference, or no passphrase at all via `tryEmptyPassphrase`. JKS and JCEKS stores use the same set of sources, with magic-byte detection between the two formats.

### Does x509-certificate-exporter need Prometheus Operator?

The `/metrics` endpoint speaks the canonical OpenMetrics text format and works with any Prometheus-compatible collector, with TLS and BasicAuth supported. The chart provides ServiceMonitor, PrometheusRule and a Grafana dashboard, so Operator-managed setups get more out of it, but the endpoint itself is standard.

### Can one x509-certificate-exporter instance monitor several clusters?

Yes. Multi-cluster from a single instance is a v4 feature, fanning in metrics from any number of clusters through distinct kubeconfigs. The related documentation also covers per-source health signals and cardinality control for that setup.

## Sources

- [enix/x509-certificate-exporter on GitHub](https://github.com/enix/x509-certificate-exporter)
- [Issues](https://github.com/enix/x509-certificate-exporter/issues)
- [License: MIT](https://github.com/enix/x509-certificate-exporter/blob/main/LICENSE)
- [README](https://github.com/enix/x509-certificate-exporter/blob/main/README.md)
- [Releases](https://github.com/enix/x509-certificate-exporter/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/enix-x509-certificate-exporter
