Kubernetes Pod Certificates vs SPIFFE/SPIRE vs cert-manager: Choosing Workload mTLS in 2026
For a decade, the only production identity Kubernetes shipped in the box was a bearer token. On 26 August 2026, that changed: Kubernetes pod certificates graduated to Stable in v1.37 “Garhwal”, alongside ClusterTrustBundles. The kubelet now generates a private key inside the node, obtains an X.509 certificate for the pod, writes both to the container filesystem, and rotates them — with no sidecar, no CSI driver, and no agent daemonset. That is a genuinely new primitive, and it lands in a market where SPIFFE/SPIRE and cert-manager already solved the same problem with far more machinery. The interesting question is not “is it good” but “which of the three is now the right default, and for whom”.
What this covers: the exact API shape and failure semantics of the new feature, a row-by-row decision matrix against SPIFFE/SPIRE and cert-manager, what each approach does about federation, revocation and non-Kubernetes workloads, and concrete “pick X when” guidance.
Context and Background
Until v1.37, if you wanted a workload inside a Kubernetes cluster to authenticate to a peer, you had three realistic options and all of them were compromises.
The first was the service account JWT. Kubernetes has projected, audience-bound, time-bound service account tokens, and they are genuinely excellent: they are written into the container before the process starts, kept fresh automatically, and understood by essentially every cloud IAM system on the planet. Their flaw is structural. A JWT is a bearer credential — as the Kubernetes v1.37 pod certificates announcement puts it, if you have the token then you are the identity it asserts. Because you must hand the token to every peer you authenticate to, every peer can replay it. Audience binding narrows the blast radius; it does not close the hole.
The second option was to bolt on a dedicated identity plane. SPIFFE defines the identity format and the delivery API; SPIRE implements it with a server, a per-node agent, node attestation and workload attestation. It is the most complete answer available, and it is also a distributed system you now operate.
The third was to treat certificates as just another Kubernetes object and let cert-manager reconcile them. That works beautifully for ingress TLS, and it is awkward for workload mTLS, because a Certificate resource writes to a Secret — an object that is cluster-visible, etcd-resident, and shared by every replica of the workload.
Each of those approaches implies a different answer to one question: who vouches for the pod? The service mesh answer — Istio’s istiod CA, Linkerd’s identity controller — is a fourth variant that most teams inherit by accident rather than choose. Pod certificates are interesting precisely because they move the attestation step into the component that already knows the truth: the kubelet that is running the pod. The rest of this post is about what that buys you and what it does not.
How Kubernetes Pod Certificates Actually Work
Kubernetes pod certificates are a kubelet-driven X.509 issuance path. A pod declares a podCertificate projected volume naming a signer. The kubelet generates a private key on the node, creates a PodCertificateRequest, waits for a signer controller to return a certificate chain, writes both into the pod filesystem, and rotates them automatically.

Figure 1: The four moving parts of a pod-certificates deployment — the pod spec, the kubelet, the API server with its NodeRestriction admission check, and a third-party signer controller that both answers requests and publishes trust anchors.
The diagram reads top to bottom as a causal chain. The pod spec is the only thing an application team writes. The kubelet does the key generation and all the API traffic. The API server is where the security invariants are enforced, not the signer. The signer controller is a separate, replaceable component that supplies both halves of the trust relationship: the leaf chain (via PodCertificateRequest.status) and the trust anchors (via ClusterTrustBundle objects, which reached Stable in the same release under KEP-3257). Note that the signer and the trust bundle publisher are the same logical actor — if you run two signers, you get two bundles.
The API surface is deliberately small
PodCertificateRequest is a new namespaced type in the certificates.k8s.io/v1 group. Its spec is immutable after creation and consists of almost nothing you write by hand: nodeName, nodeUID, podName, podUID, serviceAccountName, serviceAccountUID, signerName, maxExpirationSeconds, a DER-encoded stubPKCS10Request, and an unverifiedUserAnnotations map. The kubelet fills all of it.
Two details matter more than they look. First, the CSR is a stub: the PodCertificateRequest API reference states that CSRs generated by the kubelet are completely empty, and that most signers will ignore everything except the subject public key. The API server verifies the CSR signature during admission so the signer does not have to. The identity does not travel in the CSR — it travels in the typed spec fields, which the API server has independently validated. That inverts the classic CSR trust model, and it is the single best design decision in the KEP.
Second, unverifiedUserAnnotations is the only extensibility hook, and its name is a warning. Keys must be domain-prefixed; values are unvalidated; signers are told to deny requests carrying keys they do not recognise. Anything a pod author can put here is attacker-controlled input to your signer.
Key types are an enum, not a free-form algorithm string: RSA3072, RSA4096, ECDSAP256, ECDSAP384, ECDSAP521, and ED25519. A signer that cannot handle the requested type must deny the request with reason UnsupportedKeyType, and may suggest a workable type in the condition message. That is a small thing that will save real debugging time.
Lifetime is bounded by the API server, not the signer
maxExpirationSeconds defaults to 86400 (24 hours). The API server rejects anything below 3600 (one hour) and above 7862400 (91 days), and enforces that constraint itself rather than trusting the signer. Signers under the reserved kubernetes.io prefix will never issue longer than 24 hours.
The 91-day ceiling is a deliberate compromise with the public-PKI world, and it is also the row where this feature is weakest against SPIRE. SPIRE’s default_x509_svid_ttl is one hour, and a one-hour SVID is the entire point of the design — the credential expires before an exfiltration is useful. Pod certificates let you ask for one hour, but the floor means you cannot go shorter, and the default of 24 hours will be what most clusters actually run.
Delivery is a file, and the file format is the contract
The podCertificate projected volume source takes a signerName, a keyType, an optional maxExpirationSeconds, an optional userAnnotations map, and then either a credentialBundlePath or the pair keyPath plus certificateChainPath.
Use credentialBundlePath. The credential bundle is a single PEM file whose first block is a PKCS#8 PRIVATE KEY and whose remaining blocks are the issued chain. The upstream documentation is explicit about why: with a single file, your application makes one atomic read and always gets a key and leaf that match. With separate files you have two reads, and if a rotation lands between them you will load a private key that does not correspond to the leaf certificate — a failure that reproduces roughly once per rotation interval per replica, which is exactly the frequency that makes it hard to catch in CI and painful in production.
apiVersion: v1
kind: Pod
metadata:
name: payments-api
namespace: billing
spec:
automountServiceAccountToken: false
serviceAccountName: payments-api
containers:
- name: app
image: registry.example.com/payments-api:1.8.3
volumeMounts:
- name: workload-identity
mountPath: /run/spiffe
readOnly: true
volumes:
- name: workload-identity
projected:
sources:
- podCertificate:
signerName: "identity.example.com/spiffe"
keyType: ECDSAP256
maxExpirationSeconds: 3600
credentialBundlePath: credentialbundle.pem
- clusterTrustBundle:
signerName: "identity.example.com/spiffe"
labelSelector: {}
path: trust-anchors.pem
The clusterTrustBundle source is the mirror image. You select either a single bundle by name, or all bundles matching a signerName plus a labelSelector — the two are mutually exclusive, and an unset selector is interpreted as “match nothing” while an empty selector means “match everything”. That distinction has already bitten people: omitting labelSelector entirely does not mean “all”, it means “none”. The kubelet unifies and deduplicates the certificates from all matching bundles and stably reorders them, specifically so that applications cannot come to depend on an accidental ordering.
One behavioural constraint deserves emphasis: the pod does not start until a certificate has been issued for every podCertificate source in its spec. Your identity plane is now on the critical path of pod startup. If the signer is down, new pods in that namespace do not run. Trust-bundle sources can be marked optional: true; certificate sources cannot.
The Decision Matrix

Figure 2: The three identity planes differ less in cryptography than in who attests and how the credential reaches the process. Pod certificates attest via the kubelet and deliver via a projected volume; SPIRE attests via node plus workload attestors and delivers over a Unix domain socket; cert-manager’s SPIFFE driver attests via a ServiceAccount token and delivers via a CSI volume.
The rows below are the ones that actually change an architecture decision. Everything else — cipher suites, key algorithms, PEM handling — is common ground.
| Dimension | Kubernetes pod certificates (v1.37) | SPIFFE / SPIRE | cert-manager (+ csi-driver-spiffe) |
|---|---|---|---|
| Identity / trust-domain model | Whatever the signer encodes. Kubernetes supplies namespace, pod, node and service-account facts; the signer decides the subject. No built-in trust-domain concept. | First-class. spiffe://<trust-domain>/<workload-identifier>; trust domain is the trust root and the unit of federation. |
Certificate spec is free-form X.509. csi-driver-spiffe constrains it to a single URI SAN equal to the ServiceAccount’s SPIFFE ID within one configured trust domain. |
| Issuance path and attestation | Kubelet generates the key, creates a PodCertificateRequest; NodeRestriction admission proves the requesting node actually runs that pod. Signer never re-verifies. |
SPIRE agent performs node attestation (e.g. AWS instance identity document) then workload attestation against the process; registration entries map selectors to SPIFFE IDs. | CSI driver generates the key on the node, uses the pod’s ServiceAccount token via CSI TokenRequest to create a CertificateRequest; a separate approver enforces policy. |
| Rotation interval and failure behaviour | Signer sets beginRefreshAt; kubelet refreshes with up to 5 minutes of jitter. Refresh errors are non-fatal while the old cert is valid, then the volume fails and the pod goes unhealthy. Denied at startup = pod never starts. |
Agent renews X509-SVIDs continuously; default TTL 1 hour, ca_ttl 24 hours. Workload API streams updates; a stalled agent means stale SVIDs and eventual handshake failure. |
Renewal at 2/3 of duration by default, or renewBefore / renewBeforePercentage. csi-driver-spiffe enforces a 1-hour duration by default and rewrites the tmpfs volume. |
| Multi-cluster and federation | None. ClusterTrustBundle is cluster-scoped; cross-cluster trust means copying anchors yourself or running one signer across clusters. |
Native SPIFFE Federation: each server exposes a bundle endpoint authenticated by SPIFFE auth or Web PKI, and entries declare which domains they federate with. | Manual. Use trust-manager to distribute anchors, or chain every cluster to a shared external issuer (Vault, a corporate CA). |
| Workloads outside Kubernetes | Not supported. There is no kubelet on a VM. | The core use case. Same Workload API on VMs, bare metal and edge nodes, with platform-specific node attestors. | Partial. cert-manager can issue to anything with an API client, but csi-driver-spiffe is Kubernetes-only. |
| Revocation | None. Short lifetimes only. Denying future requests stops renewal, not the live certificate. | None by design. Short TTLs plus deleting the registration entry, which stops renewal within one TTL. | CRL/OCSP only if the backing CA provides it; in practice also short lifetimes. |
| Service-mesh interaction | Orthogonal today. Istio and Linkerd run their own CAs; Istio has experimental ClusterTrustBundle support. Useful for non-mesh and mesh-external traffic. |
Strong. Envoy consumes SPIFFE SVIDs over SDS; Istio can be configured against a SPIRE trust domain. | Strong via istio-csr, which makes cert-manager the CA behind istiod. |
| Operational cost | Lowest ongoing cost, but you must write or adopt a signer controller — core Kubernetes ships none. | Highest. A stateful server with a datastore, a DaemonSet agent, attestor configuration and a registration-entry lifecycle. | Middling. cert-manager is probably already installed; csi-driver-spiffe adds a DaemonSet and an approver, plus RBAC you must get right. |
Three rows deserve more than a table cell.
The “no signer ships in core” row is the headline caveat. The feature is Stable, the machinery is Stable, and Kubernetes supplies zero signers. The API reference documents one well-known name, kubernetes.io/kube-apiserver-client-pod, and describes it as currently unimplemented. The upstream blog is candid that trying the feature today means installing a third party signer, and points at Tinycert — explicitly “not a full production solution”. So in September 2026, adopting Kubernetes pod certificates means adopting somebody’s signer, or writing one. That is a real cost, and it is the cost SPIRE and cert-manager have already paid on your behalf.
The federation row is where SPIRE keeps winning. ClusterTrustBundle is cluster-scoped, and there is no mechanism in-tree for one cluster to learn another’s anchors. SPIRE’s federation model — a bundle endpoint per server, authenticated either by SPIFFE auth against the peer’s own bundle or by Web PKI using a publicly trusted certificate — solves a problem that pod certificates do not currently attempt.
The revocation row is a tie, and that is fine. None of the three gives you working revocation. CRLs and OCSP are, for internal mTLS at scale, a distributed-systems liability rather than a control. All three converge on the same real answer: make certificates short enough that expiry is your revocation. The practical difference is the floor — SPIRE defaults to one hour, csi-driver-spiffe enforces one hour, and pod certificates cannot go below one hour but default to 24.
Rotation, Failure Modes, and the Reload Contract
Rotation is where a workload-identity system earns or loses its keep, because rotation is the only part of it that runs every day.

Figure 3: Issuance and refresh as the kubelet sees it. Note that the refresh is a brand-new PodCertificateRequest, not an update of the old one — requests are single-use and their spec is immutable.
The sequence is worth walking through because each arrow hides a failure mode.
The kubelet generates the key in memory and never writes it anywhere but the pod’s volume. It then creates a PodCertificateRequest naming itself, the node UID, the pod UID and the service account UID. The NodeRestriction admission plugin checks that the request was created by system:node:<nodeName> and that the pod is actually scheduled there. This is the mechanism that makes a compromised node’s blast radius equal to the pods it already runs — it cannot mint identities for workloads elsewhere in the cluster. It is the same invariant that makes projected service account tokens safe, reused.
The signer sets certificateChain, notBefore, notAfter and beginRefreshAt in one /status update. All four become immutable. beginRefreshAt is explicitly a hint: the kubelet may start earlier or later. In practice the kubelet applies a randomised jitter of up to five minutes, which exists to stop hundreds of volumes on a node from converging on the same refresh instant and stampeding the signer. If you build a signer, size it for the un-jittered worst case anyway — five minutes of spread across a 24-hour lifetime is not much smoothing.
Failure handling is asymmetric in a way that is easy to misread:
- Denied at initial issuance is fatal. The volume setup returns an error and the pod does not start. The reasoning in the KEP is that a signer that denies is not going to change its mind on retry.
- Failed is also treated as permanent by the kubelet, on the assumption that a Deployment, Job or DaemonSet will supply the retry by creating a replacement pod. If you run bare pods, you own that retry.
- Refresh errors while the current certificate is still valid are logged and swallowed. This is the right call: a signer outage shorter than the remaining lifetime is invisible.
- Refresh errors after expiry are returned from the volume SetUp function, so the pod goes unhealthy. The KEP’s justification is direct — better to fail noisily than to keep running with an expired credential.
That last transition is the one to alert on, and the KEP gives you the signal. The kubelet exposes a gauge vector of pod-certificate states faceted by signer_name and state, where state is one of fresh, overdue_for_refresh (more than ten minutes past beginRefreshAt), expired, failed or denied. It also emits Events when a request it created reaches Failed or Denied, when a running pod is more than twenty minutes past its beginRefreshAt without a refresh, and when a certificate expires unrefreshed. Any non-zero count in overdue_for_refresh is a signer availability incident in progress; expired means you are already taking traffic errors.
The reload contract is the application’s problem, and this is the part teams underestimate. The kubelet rewrites the credential bundle in place. Nothing restarts. Nothing signals your process. If your TLS stack loads certificates once at startup — which is the default in almost every framework — your service will run happily on a stale certificate until it expires and then fail every handshake simultaneously across all replicas, roughly 24 hours after deploy. The correct pattern is a GetCertificate / GetClientCertificate callback backed by a cached bundle that an inotify watch or a short poll refreshes. Trust anchors have the same requirement: the kubelet updates clusterTrustBundle files whenever the selected bundles change, and your verifier must pick that up or you will fail to validate peers after a CA rotation.
This is also the clearest ergonomic win for SPIRE. The SPIFFE Workload API is a streaming gRPC API — the agent pushes new SVIDs and bundles to a connected client, and the official libraries hand you a TLS config that is already wired for rotation. A file that changes underneath you is a weaker contract than a stream that tells you it changed. The upstream project knows this, which is why the v1.37 blog points at a SPIFFE Filesystem Delivery draft standard intended to make filesystem-delivered SPIFFE credentials as easy to consume as the socket.
Federation, Workloads Off-Cluster, and the Revocation Question
If your estate is one cluster, skip this section. If it is not, this section is the decision.
SPIFFE federation is a specified protocol, not a pattern. Each SPIRE server can expose a bundle endpoint carrying its trust domain’s anchors, authenticated one of two ways. With SPIFFE auth, the peer validates the endpoint using a bundle it already holds — bootstrap once, then the relationship self-maintains as keys rotate. With Web PKI auth, the endpoint presents a publicly trusted certificate (SPIRE can obtain one via ACME), which means you must own the DNS name and it must resolve to the server. Workloads then declare which foreign trust domains they federate with in their registration entries, and the Workload API hands them the foreign anchors alongside their own.
Pod certificates have no equivalent. ClusterTrustBundle is cluster-scoped, and nothing in-tree ships anchors between clusters. You have two workable shapes. Either run a single signer service outside the clusters and have each cluster’s signer controller act as a thin adapter to it — one CA, many clusters, uniform anchors — or accept a mesh of bundles and reconcile them with your own controller. The first is cleaner and is how most organisations will end up doing it. It also quietly reintroduces a centralised issuing service with its own availability requirements, which was the thing pod certificates appeared to eliminate.
Off-cluster workloads are simply out of scope for pod certificates. There is no kubelet on a bare-metal gateway, an industrial edge box, or a legacy VM running a payments daemon. SPIRE was designed for exactly this: the same Workload API, the same SPIFFE IDs, the same trust bundles, with a platform-appropriate node attestor underneath. If you are building a zero-trust network architecture that has to span Kubernetes and non-Kubernetes compute — which describes almost every industrial and edge deployment — a Kubernetes-only identity plane is disqualifying on its own, regardless of how elegant it is inside the cluster. The SPIFFE/SPIRE workload identity architecture post goes deeper on the attestor and registration-entry model that makes this possible.
Revocation deserves a blunt statement, because vendors are vague about it. None of these three systems revokes certificates in any operationally meaningful sense. CRL distribution and OCSP both put a synchronous dependency on a remote service in the hot path of every handshake, and OCSP stapling shifts rather than removes the problem. For internal service-to-service mTLS at scale, the industry has settled on short lifetimes as the revocation mechanism, and all three systems reflect that.
What differs is your time-to-contain. With SPIRE, delete the registration entry and the identity stops being reissued within one TTL — one hour by default. With csi-driver-spiffe, revoke the ServiceAccount’s ability to create CertificateRequest objects, or tighten the approver policy, and renewal stops within the enforced duration. With pod certificates, make your signer deny the workload and renewal stops at the next beginRefreshAt; with the 24-hour default, worst case is close to a full day of a compromised credential remaining valid. If that number matters to you, set maxExpirationSeconds: 3600 and have your signer issue at the floor. You will pay for it in signer QPS — a 10,000-pod cluster at one-hour certificates is roughly 2.8 issuances per second sustained, plus the thundering-herd behaviour after any node-level restart.
Where the Service Mesh Fits
Most teams that need workload mTLS already have some of it, because they run a mesh. Understanding what the mesh already does prevents you from buying the same thing twice.
Istio provisions identity itself. istiod offers a gRPC service that accepts certificate signing requests; the istio-agent alongside each proxy generates the private key and CSR, sends it with its credentials, and istiod’s CA validates and signs. The agent then serves the key and certificate to Envoy over the Secret Discovery Service API, monitors expiry, and repeats. Identity is the Kubernetes service account, surfaced in authorization policy as principals like cluster.local/ns/default/sa/curl. Recent Istio versions have added experimental support for consuming ClusterTrustBundle, which is the first sign of the two worlds meeting.
Linkerd is architecturally similar and simpler. Its identity control-plane component is a CA that issues a certificate bound to the pod’s ServiceAccount. Each proxy generates its private key into a tmpfs emptyDir so it never leaves the pod, submits a CSR containing its service account token for validation, and receives a certificate. Linkerd’s documentation is specific that these certificates expire after 24 hours and are rotated automatically.
Read those two descriptions next to the pod-certificates flow and the overlap is obvious: generate a key on the node, prove the pod’s service account identity, get a short-lived certificate, rotate it. The mesh does it with a control-plane CA and a data-plane agent; Kubernetes now does it with the kubelet and a signer controller.
The practical consequence is that if you already run a mesh in strict mTLS mode, pod certificates do not replace it and should not try to. Where they help is everything the mesh does not cover: traffic that bypasses the proxy, workloads you deliberately left unmeshed, clients calling external services that want client certificates, and — most usefully — applications that want to do their own TLS rather than delegate to a sidecar. In an ambient or sidecar-less topology, where per-pod proxies are being removed, an in-process certificate obtained from a projected volume is a clean way to keep application-level mTLS.
The cert-manager ecosystem offers a different kind of integration: istio-csr makes cert-manager the certificate authority behind istiod, so mesh certificates chain to the same enterprise PKI as everything else. If your constraint is “the security team must see one CA hierarchy”, that path is more valuable than either of the alternatives, and it is worth reading alongside how you already handle Kubernetes secrets with the External Secrets Operator, since the two decisions tend to share an owner.
Operational Cost, Honestly Accounted
Cost comparisons in this space are usually written by whoever is selling the thing. Here is the accounting I would actually use.
SPIRE’s cost is the server. It is stateful, it needs a datastore (SQLite by default, which you will replace with PostgreSQL or MySQL), it holds the CA key, and it is on the critical path for every SVID renewal in the trust domain. Add the agent DaemonSet, node attestor configuration per platform, and — the part that consumes the most engineering time — the registration-entry lifecycle. Every workload needs an entry mapping selectors to a SPIFFE ID, and those entries have to be created, updated and deleted in step with deployments. Teams that succeed with SPIRE build automation for this early; teams that fail do it by hand.
cert-manager’s cost is mostly already sunk. It is installed in a very large fraction of clusters for ingress TLS. Adding csi-driver-spiffe means a DaemonSet plus a dedicated approver Deployment, and it means confronting one uncomfortable requirement: every pod using the driver must have RBAC permission to create CertificateRequest resources, because the driver impersonates the pod’s ServiceAccount. cert-manager’s own documentation flags the implication — a compromised pod with that permission can create arbitrary CertificateRequest objects referencing arbitrary issuers, and the only thing standing in the way is approval policy. If you also run approver-policy with a permissive rule for, say, an ACME issuer, you have built a path from pod compromise to a publicly trusted certificate. That is a configuration audit you must actually perform, not a theoretical concern.
Pod certificates have the lowest steady-state cost of the three: no DaemonSet, no per-workload registration entries, no extra volume driver, and delivery machinery maintained by the Kubernetes project. Against that, you must supply a signer controller. A minimal one is perhaps a few hundred lines — watch PodCertificateRequest, filter by signerName, apply policy against the typed spec fields, sign with a CA key, patch /status. The hard parts are the ones that always bite: where the CA key lives (a KMS or HSM, not a Secret), how you rotate it, how the controller stays available during a control-plane upgrade, and how you scale it to your issuance rate. You are writing a small but genuinely security-critical service, and the policy layer you build there is the same class of problem as policy-as-code with Kyverno or OPA Gatekeeper — get the admission story wrong and the cryptography does not save you.
Trade-offs, Gotchas, and What Goes Wrong
The startup dependency is real and under-discussed. A pod with a podCertificate source does not start until issuance succeeds. Your signer is now a hard dependency of every pod launch in scope. A control-plane upgrade, a signer rollout with a bad image, or a CA key that a KMS refuses to unwrap will present as a cluster-wide inability to start pods, not as a TLS problem. Run the signer with multiple replicas and a PodDisruptionBudget, exclude it from the scope of its own signer, and test the cold-start path where the signer and its workloads come up together.
Silent staleness is the most likely production incident. Because refresh failures are non-fatal while the certificate is valid, a broken signer produces no application symptom for up to 24 hours and then produces a correlated failure across every pod at once. The overdue_for_refresh state is your only early signal. Alert on it at a low threshold.
Separate key and chain paths will eventually hand you a mismatched pair. The API documents this and offers the credential bundle as the fix. Use it. If you are forced onto separate paths by a library that cannot parse a multi-block PEM, your loader must verify that the leaf’s public key matches the private key and re-read until it does.
labelSelector semantics on trust bundles are a trap. Unset means match nothing; set-but-empty means match everything. An omitted selector will look like a signer outage.
Denied is terminal, and the message field is your only diagnostic. When a signer denies with UnsupportedKeyType, the pod fails to start with an error that surfaces as a volume mount failure. Make your signer write a useful message, and make sure whoever operates the cluster knows to look at the PodCertificateRequest object rather than the pod events alone.
Do not assume automountServiceAccountToken: false is now safe. Pod certificates replace the bearer token for workload-to-workload authentication, not for talking to the Kubernetes API. If your application uses client-go, it still needs the token.
The 91-day ceiling is not an invitation. It exists so that signers backed by conventional PKI can participate. Treating it as a target reintroduces the long-lived-credential problem the feature was built to remove.
Practical Recommendations

Figure 4: The four questions that actually determine the answer, in the order they should be asked. Scope comes first because it is the only one of the four that can disqualify an option outright.
Answer the scope question before anything else, because it is the only genuinely binding constraint. Everything downstream is a cost-and-taste judgement.
Pick SPIFFE/SPIRE when any workload that needs identity does not run in Kubernetes, or when you must federate across organisational or regulatory boundaries with separate trust roots. Also pick it when you want the strongest available attestation — SPIRE’s combination of node and workload attestation verifies properties of the running process, where pod certificates verify what the API server already knows about the pod. Budget for the registration-entry automation from day one.
Pick cert-manager when the binding requirement is that every certificate chains to an existing enterprise or public PKI, or when you want one CA story across ingress, mesh and workload identity. istio-csr plus trust-manager is a coherent architecture, and it lives inside a component you probably already operate. Audit the CertificateRequest RBAC and approver policy before you ship.
Pick Kubernetes pod certificates when your estate is Kubernetes-only, you want the fewest moving parts, and you are willing to own a signer controller. This is the right default for new single-cluster or single-operator platforms and the natural replacement for hand-rolled init-container CSR scripts. It is not yet the right choice if you need a turnkey experience today, because core ships no signer.
Keep the mesh CA when you already run Istio or Linkerd in strict mTLS and your traffic is fully meshed. Add pod certificates for the traffic the proxy does not carry rather than migrating what it does.
Before you commit, run this checklist:
- Confirm the target clusters are on v1.37 or later; the
PodCertificateRequestfeature gate is enabled by default and locked on at Stable. - Decide the certificate lifetime deliberately and set
maxExpirationSecondsexplicitly — do not inherit 24 hours by accident. - Use
credentialBundlePath, never the split paths, unless a library forces your hand. - Verify every application reloads certificates and trust anchors without a restart, and test it by forcing a rotation.
- Store the signer CA key in a KMS or HSM; never in a Kubernetes Secret.
- Alert on
overdue_for_refreshand on Failed/Denied Events, not just on expiry. - Load-test the signer at your full fleet’s issuance rate plus a simultaneous node-restart burst.
Frequently Asked Questions
Are Kubernetes pod certificates generally available?
Yes. Pod certificates (KEP-4317) and ClusterTrustBundles (KEP-3257) both graduated to Stable in Kubernetes v1.37, released 26 August 2026. The KEP records alpha in v1.34, beta in v1.35 and stable in v1.37, and its kep.yaml status is implemented. The PodCertificateRequest feature gate on kube-apiserver is enabled by default and locked to true, so the feature cannot be disabled. What is not available is a signer: Kubernetes ships none in core, so a working deployment needs a third-party or in-house signer controller.
Do pod certificates replace service account tokens?
No. They replace bearer tokens for workload-to-workload authentication, where proof-of-possession is strictly better than a replayable token. They do not replace the projected service account token you use to authenticate to the Kubernetes API itself, nor the JWT federation path that backs pod-to-cloud IAM on the major providers. Most clusters will run both for a long time: a certificate for peer mTLS, a token for the API server and cloud IAM.
How often do pod certificates rotate, and what happens if rotation fails?
The signer sets beginRefreshAt; the kubelet refreshes then, plus up to five minutes of random jitter. Lifetimes are bounded by the API server at 1 hour minimum and 91 days maximum, defaulting to 24 hours, and kubernetes.io signers never exceed 24 hours. While the existing certificate is still valid, refresh errors are logged and ignored. Once it expires, the volume setup fails and the pod goes unhealthy — deliberately noisy, so a stale credential never keeps serving traffic.
Can pod certificates issue SPIFFE-compatible identities?
Yes, because the signer controls the certificate contents entirely. A signer can emit a URI SAN of the form spiffe://<trust-domain>/ns/<namespace>/sa/<service-account> derived from the typed fields in the request spec. The upstream reference signer does exactly this, and the Kubernetes project has stated an intent to eventually ship a built-in SPIFFE client-certificate signer. What you do not get from Kubernetes is the rest of SPIFFE: no Workload API, no federation protocol, no off-cluster attestation.
Is SPIRE still worth running if pod certificates exist?
If any part of your estate is not Kubernetes, yes, unambiguously — there is no kubelet on a VM or an edge gateway, and SPIRE’s Workload API works identically everywhere. SPIRE also remains the only one of the three with a specified federation protocol for crossing trust domains, and its workload attestors verify properties of the running process rather than facts the API server already holds. For a single Kubernetes cluster with no external workloads, SPIRE is now harder to justify than it was a year ago.
What is the difference between csi-driver-spiffe and pod certificates?
Both deliver a per-pod key and certificate to the node without the key leaving it. csi-driver-spiffe runs a DaemonSet that uses the pod’s ServiceAccount token to create a cert-manager CertificateRequest, which a dedicated approver validates against a fixed policy — default one-hour duration, a single URI SAN, one trust domain. Pod certificates move that flow into the kubelet and the API server, removing the CSI driver, the impersonation, and the requirement that every pod hold CertificateRequest create permission.
Further Reading
- SPIFFE and SPIRE workload identity architecture for zero trust — the attestation and registration-entry model in depth.
- Kubernetes secrets management with the External Secrets Operator — where certificates fit against the wider secret-delivery problem.
- Zero trust network architecture implementation guide — the control framework workload identity plugs into.
- Kubernetes policy as code: Kyverno vs OPA Gatekeeper — the admission layer your signer policy has to coexist with.
- Kubernetes v1.37: Pod Certificates and Cluster Trust Bundles — the upstream announcement by Taahir Ahmed.
- PodCertificateRequest API reference and KEP-4317 — the normative field semantics and design rationale.
- SPIFFE concepts and cert-manager csi-driver-spiffe — the two incumbent designs, in their own words.
By Riju — about
