TYPO3 on Kubernetes under IT-Grundschutz, part 5: mTLS on every hop
TLS to the outside is a given. Inside the cluster, though, traffic often runs in plain text because “the internal network” is considered trustworthy. With a high protection need that is not enough. Every connection should be encrypted, and both sides should prove who they are.
This part shows how mTLS works on every hop: with a CA per tenant, short-lived certificates and a reload that needs no restart.
Part 5 of 14 of the series "TYPO3 on Kubernetes under IT-Grundschutz". The overview of all parts is in part 1.
01 — Which hops get encrypted
A typical TYPO3 installation in a cluster has four internal hops:
- from the ingress to the tenant's reverse proxy,
- from the reverse proxy to the PHP runtime, for example FrankenPHP,
- from the application to the database,
- from the application to the cache, for example Valkey.
The public certificate ends at the ingress. From there, an internal one takes over. mTLS means that not only the server presents a certificate, but the client does too. A pod without a matching certificate cannot establish a connection, even if the network would allow it.
mTLS does not replace the NetworkPolicies from part 6, it complements them. The policy decides who can reach whom. The certificate proves who is talking.
02 — A CA per tenant
cert-manager issues the certificates. Each tenant gets its own CA, modelled as an Issuer in the tenant's namespace. A certificate from tenant A is not accepted by tenant B's services. The separation therefore reaches all the way into cryptography.
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: app-mtls
namespace: tenant-a
spec:
secretName: app-mtls
duration: 24h
renewBefore: 8h
privateKey:
algorithm: ECDSA
size: 256
rotationPolicy: Always
usages: [server auth, client auth]
dnsNames:
- app.tenant-a.svc.cluster.local
issuerRef:
name: tenant-a-ca
kind: Issuer
Why only 24 hours
A short-lived certificate quickly loses its value if the key leaks. Revocation lists or OCSP are not needed for that. Renewing after 16 hours leaves an eight-hour buffer. And the short lifetime forces automation that works. If renewal depends on a manual step, that shows up by the next day at the latest.
With rotationPolicy: Always, every renewal also creates a new key. With cert-manager, private keys live only as a secret in the cluster and are mounted into the pod, never baked into the image.
Since Kubernetes 1.37, without a secret
Kubernetes 1.37 ships pod certificates. The kubelet generates the key itself, fetches the certificate from a signer and writes both into a volume, so the key never sits in a secret. You do have to provide the signer yourself, because cert-manager cannot issue these certificates yet (cert-manager#8378). A signer of its own per tenant keeps the separation just as a CA per tenant does. How I built one and moved a whole cluster's mesh onto it is covered in two field notes: Pod certificates instead of shared secrets and The whole mesh on pod certificates. For most installations, cert-manager remains the mature default.
03 — Reloading without a restart
cert-manager renews the secret. The process that uses the certificate still has to load it again. This is the typical pitfall: renewal works, reloading does not, and after 24 hours the connection fails.
Each component has its own way:
- Caddy and FrankenPHP reload their configuration through the admin API. A small sidecar notices the new certificate and triggers it.
- MariaDB rereads its certificates with
FLUSH SSL, without dropping connections. - Valkey picks up new certificate files when its TLS settings are set again with
CONFIG SET. - Where no such path exists, a tool like Stakater Reloader restarts the pods on a changed secret, in a rolling fashion.
The other side verifies the client certificate. In Caddy it looks like this:
tls /tls/tls.crt /tls/tls.key {
client_auth {
mode require_and_verify
trust_pool file /tls/ca.crt
}
}
The database demands encrypted connections with require_secure_transport=ON, and the application user additionally needs a certificate via REQUIRE X509. Valkey listens only on its TLS port and demands a client certificate with tls-auth-clients yes.
Monitoring expiry
Monitoring the secret is not enough. What matters is the certificate the service actually serves. A probe that opens the connection and measures the remaining lifetime alerts before the buffer runs out.
04 — Service mesh or explicit mTLS
A service mesh like Istio or Linkerd handles mTLS automatically, through sidecars or an ambient mode. Policies and telemetry come on top. The price is an additional control plane, more moving parts and harder debugging.
Explicit mTLS is the leaner option. Every hop is configured visibly, and there is no layer in between. In return, each component needs its own setup. With few services per tenant, as in a TYPO3 installation, the explicit model is usually enough. With many services and changing connections, a mesh pays off sooner.
How it maps to IT-Grundschutz
Algorithms, key lengths and lifetimes belong in the cryptography concept under CON.1. Encryption between pods supports network separation (APP.4.4.A7) and micro-segmentation for an increased protection need (APP.4.4.A18). Keeping keys only as mounted secrets and never in the image contributes to secure storage of credentials (SYS.1.6.A8) and to protecting configuration data (SYS.1.6.A20).
Frequently asked questions
cert-manager or pod certificates?+
cert-manager is the mature default: certificates live in a secret, and a reloader restarts pods whenever a service does not pick up the renewed certificate on its own. Pod certificates need no secret, and software that rereads its files no longer needs a reloader. In exchange you need a signer of your own and some care. A denied request stays denied, so the grant on the signer has to be live before you switch a workload over. And the kubelet writes the files as 0640 for the runAsUser, which PostgreSQL refuses for a key it owns. So measure, for each component, what it actually does with the certificate.
Why mTLS when there are NetworkPolicies already?+
NetworkPolicies decide which pod can reach which. mTLS verifies identity cryptographically and protects the content from eavesdropping. These are two independent layers. If one fails, the other still holds.
Why certificates valid for only 24 hours?+
A stolen key is worthless after a day at most, without any revocation lists. The short lifetime also forces automation that really works. A fault in renewal shows up the next day, not after a year.
Do I need a service mesh for mTLS?+
Not necessarily. With few services per tenant, explicit mTLS with cert-manager stays manageable and easy to follow. A mesh pays off with many services and changing connections, but it brings its own control plane.
What happens if renewal fails?+
Renewing after 16 hours leaves an eight-hour buffer. An alert on the remaining lifetime of the certificate that is actually served fires within that window. Monitoring only the secret misses a failed reload.
Conclusion
mTLS in a cluster is not a service mesh project but a matter of a clean chain: a CA per tenant, short-lived certificates, a reliable reload in every component and monitoring of the certificate that is actually served. The short lifetime is not a risk but a test. It shows every day whether the automation works.
The series continues with part 6: network and tenant separation. Back to part 4: no CVEs above 7.
I set up mTLS on every hop in your cluster. With certificates that renew themselves.
A CA per tenant with cert-manager, short-lived certificates, reloading without a restart for proxy, PHP, database and cache, and monitoring that warns before expiry.
Platform operations rather than advice on paper: if you want, I run the certificate chain for you on an ongoing basis.
About the author

Kai Ole Hartwig
Programming since 2002 – self-taught, set up my own business with KO-Web in 2012. Over 100 projects, with a focus on security, performance, automation and quality. Today freelance: DevSecOps consulting, training and software development.
