Kubernetes Pod Certificates mTLS: GA Without a Signer to Run

Pod certificates reached stable in Kubernetes v1.37 and the only documented in-core signer is unimplemented. Here is what you can actually remove this quarter, and what still needs cert-manager, SPIRE or a signer you write yourself.

By VVV Ops ·

Your cluster has shipped the plumbing for pod-to-pod mTLS and none of the pipe. The Kubernetes pod certificates mTLS path reached stable in v1.37, released on 26 August 2026, and the write-ups that followed told teams they could finally delete cert-manager, or SPIRE, or the service mesh they only run for certificate rotation. We read the API reference instead of the headlines. The certificate issuance path is real, it is well designed, and the Kubernetes project ships nothing to answer the requests it generates. Here is what you can actually remove this quarter, which is less than you were told.

What went stable in v1.37, and what is still an empty slot

Two things graduated. The podCertificate projected volume source, which first appeared in v1.34, is stable since v1.37 and enabled by default. So is the clusterTrustBundle source, which has been around since v1.29. Together they give the kubelet a way to generate a private key inside the node, get it signed, and drop the result into your container's filesystem, plus a way to publish the matching CA roots.

The part that did not graduate is the certificate authority. In the release announcement, feature author Taahir Ahmed writes that the Kubernetes project does not yet ship any Pod Certificate signers in core. The API reference is blunter. It documents exactly one well-known signer name, kubernetes.io/kube-apiserver-client-pod, and then says of it: "It is currently unimplemented."

A PodCertificateRequest addressed to a signer that nobody runs does not fail loudly. It sits in the namespace with no status.certificateChain, and the pod waits for a file that never arrives. Treat the GA label as covering the transport, not the trust.

How the issuance flow works

Four components take part: your application, the kubelet, the kube-apiserver, and a signer controller you supply.

When a pod with a podCertificate projection is scheduled, the kubelet generates a private key matching the keyType you asked for, then creates a PodCertificateRequest in certificates.k8s.io/v1 addressed to your signerName. The spec it writes is pinned to reality: nodeName, nodeUID, podName, podUID, serviceAccountName and serviceAccountUID are all filled in by the kubelet and immutable after creation. The built-in node restriction admission plugin stops one compromised node from requesting certificates for pods scheduled elsewhere, so the node isolation story is enforced in the API server rather than in your signer.

Your signer controller watches for these objects, decides whether to issue, and writes status.certificateChain, status.notBefore, status.notAfter, and status.beginRefreshAt. That last field is the rotation hint telling the kubelet when to start the cycle again. If the signer will not issue, it sets a condition of type Denied or Failed; a key type it does not support gets Denied with reason UnsupportedKeyType.

Here is the pod side, which is the easy half:

# Illustrative. Replace the signer with one that is actually running in your cluster.
apiVersion: v1
kind: Pod
metadata:
  name: payments-api
  namespace: acme-prod
spec:
  containers:
    - name: api
      image: registry.example.internal/acme/payments-api:1.4.2
      volumeMounts:
        - name: workload-identity
          mountPath: /var/run/acme/tls
          readOnly: true
  volumes:
    - name: workload-identity
      projected:
        sources:
          - podCertificate:
              signerName: acme.example.internal/workload-spiffe
              keyType: ECDSAP256
              maxExpirationSeconds: 86400
              credentialBundlePath: credentials.pem
          - clusterTrustBundle:
              signerName: acme.example.internal/workload-spiffe
              path: roots.pem

Valid keyType values are ED25519, ECDSAP256, ECDSAP384, ECDSAP521, RSA3072 and RSA4096. Use credentialBundlePath rather than the separate keyPath and certificateChainPath fields unless a library forces your hand. The bundle is one PEM file whose first block is a PKCS#8 PRIVATE KEY and whose remaining blocks are the CERTIFICATE chain, so a reload is one atomic read instead of two files that can disagree mid-rotation.

The rotation your application has to handle

Nothing here keeps a long-lived certificate on disk, and that is the point. maxExpirationSeconds defaults to 86400, one day. The API server rejects anything below 3600, one hour, and caps the field at 7862400, which is 91 days. Any signer under the kubernetes.io prefix is restricted to 24 hours whatever you ask for, so when the in-core signers do arrive, one day is your ceiling.

That turns a property most teams never tested into a daily event. A 24-hour lifetime with a beginRefreshAt partway through means your process re-reads its credentials roughly 365 times a year per pod. Applications that read TLS material once at startup will run fine for a day and then fail closed, which is the worst possible failure shape: it passes every smoke test and breaks in the middle of the night, everywhere at once.

The fix is a GetCertificate or GetClientCertificate callback plus an inotify watch or a poll on the bundle path. The kubelet writes projected files through a symlink swap, so an open-and-read during rotation gives you the old contents or the new contents, never a half-written file. Test it properly, by forcing a rotation under load and watching for handshake errors, not by reading the code and declaring it fine. We have had more client incidents from applications that cached a certificate forever than from anything the issuance path did wrong.

Check your managed control plane before you plan the quarter

Before any of this matters, check whether your platform offers v1.37 at all. As of early October 2026:

| Platform | v1.37 status | Where that leaves pod certificates | |---|---|---| | Amazon EKS | Released 1 October 2026, standard support to 1 December 2027 | Available after a control plane upgrade | | GKE | Rapid channel since 4 September 2026; Regular, Stable and Extended do not list it | Testable on Rapid, not where production sits | | AKS | Preview September 2026, GA scheduled October 2026 | Weeks away on paper | | Self-managed (kubeadm, kOps, Talos) | Available on upgrade | Available now |

If production sits on GKE Regular or Stable, the answer for this quarter is no, and any migration plan that assumes a Q4 cutover should stop there. EKS shipped v1.37 on 1 October 2026, so the blocker on AWS is your own upgrade backlog rather than the platform. Either way, if the version gap tempts you to sit on an old release, price it first: falling behind standard support has a per-cluster hourly charge on both EKS and GKE.

So what can you delete?

Nothing yet, in most clusters. The decision, row by row:

| You run this today | For this reason | Replace with pod certificates? | |---|---|---| | Service account JWTs for pod-to-cloud auth | Federation into AWS, GCP and Azure IAM | No. JWT federation is what cloud IAM accepts; certificates have no path there | | cert-manager for ingress and public certs | Public WebPKI, ACME, Let's Encrypt | No. Different problem entirely, and unaffected | | cert-manager issuing internal pod certs from a private CA | Internal mTLS without a mesh | Not yet. Issue 8378 is open with no PR | | SPIRE for SPIFFE workload identity | Attestation, federation, non-Kubernetes workloads | No. Pod certificates cover one cluster; SPIRE's value is everything outside it | | Istio or Linkerd mTLS | Mesh policy, observability, traffic shifting | No, unless rotation was the only reason you adopted it | | Hand-rolled init container that mints certs from Vault | Nobody owns it and it breaks on renewal | This is the real candidate. Rewrite it as a signer |

The one unambiguous win is the home-grown certificate sidecar. If you have an init container that pulls a cert from Vault, or a CronJob that writes into a Secret, or anything mounting a certificate out of a Secret that a human rotates, pod certificates give you a supported path that no longer stores private keys in etcd at all. That alone is worth the work, and it is where we would start.

For everything else, your gating dependency is a production signer. The reference implementation is Tinycert by the feature's author, which exposes ahmedtd.github.io/tinycert-service for DNS SANs and ahmedtd.github.io/tinycert-spiffe for SPIFFE identities. It is Apache-2.0 and its own README calls it a toy certificate system for education. Do not put it in front of revenue.

That leaves two options. Wait for cert-manager, SPIRE or your mesh vendor to implement the signer contract, or write a controller yourself. The contract is small: watch PodCertificateRequest objects, verify the pod and service account identity the kubelet recorded, sign, fill in the status fields, and publish your CA as a ClusterTrustBundle. A competent platform team can build that in a sprint. Owning a certificate authority for the next five years is the part to think hard about, and for most organisations the answer is to wait for someone else to maintain it.

A 90-day plan that does not waste the quarter

Weeks 1 and 2 are inventory. List every place a private key reaches a pod today: Secret mounts, init containers, mesh sidecars, cert-manager Certificate resources, Vault agents. Tag each one with who rotates it and what happens when rotation fails. Most teams find two or three they had forgotten, and that list is worth having whether or not you adopt anything.

Weeks 3 to 6 are a spike on one non-production cluster you control, on EKS, GKE Rapid or a self-managed v1.37. Install Tinycert, mount a podCertificate volume into one service, and then do the only test that matters: force a rotation while traffic is flowing and confirm the application picks up the new bundle without dropping connections. If it does not, you have found work that needs doing regardless of which issuer you eventually pick.

Weeks 7 to 12 are the build-or-wait decision, written down. Subscribe to cert-manager issue 8378. Ask your mesh vendor for a date in writing. If the answer is vague and you have a Vault-based certificate sidecar nobody wants to own, that is the business case for writing your own signer, and it is a better case than "1.37 made it stable".

What we would not do is sequence this ahead of the certificate work that already has a deadline. Public TLS validity is being cut again in March 2027, and the renewal arithmetic changes before then. Internal mTLS has no regulator attached to it. If both land in the same quarter, the public one wins. Pod certificates also slot neatly into an existing zero trust programme rather than justifying one on their own, which is the framing we use when identity becomes the control plane.

When to Get Help

Most teams do not need help mounting a projected volume. They need help answering two questions honestly: whether their applications survive daily credential rotation, and whether running a certificate authority is a thing their platform team should own for the next five years. We have watched both decisions go badly, usually because the migration was scoped as a YAML change.

If you are mapping where private keys reach your pods today, deciding whether to write a signer or wait for cert-manager, or trying to get off a hand-rolled certificate sidecar before it breaks again, talk to us. We will tell you if the answer is to wait.

Tags: kubernetes pod certificates mtls, podcertificaterequest signer, clustertrustbundle kubernetes, kubernetes workload identity, secure kubernetes deployments, devsecops implementation