Kai Ole Hartwig
7 min read
Low
By

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:

  1. from the ingress to the tenant's reverse proxy,
  2. from the reverse proxy to the PHP runtime, for example FrankenPHP,
  3. from the application to the database,
  4. 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:

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.

Book a call →

About the author

Photo of Kai Ole Hartwig.

Kai Ole Hartwig

Freelance DevSecOps consultant · OnlyOle Consulting

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.