Introduction
Securing internal communication between services in Kubernetes is a critical security practice. Mutual TLS (mTLS) ensures encrypted traffic while also verifying the identity of both the client and server. In this guide, we will configure mTLS between two microservices using Cert-Manager for automated certificate issuance and renewal.
Problem Statement
By default, Kubernetes services communicate in plaintext, making them vulnerable to man-in-the-middle attacks. We need a solution that:
- Encrypts communication between services.
- Ensures only trusted services can talk to each other.
- Automates certificate management to avoid manual rotation.
Solution: mTLS with Cert-Manager
We will deploy:
A Certificate Authority (CA) to issue certificates.
A Kubernetes Issuer to generate TLS certificates.
Two microservices (App One and App Two) configured with mTLS.
A test pod to verify secure service-to-service communication.
Step 1: Install Cert-Manager
Cert-Manager automates TLS certificate lifecycle management. If you haven’t installed it yet, deploy it using Helm:
helm repo add jetstack https://charts.jetstack.io
helm repo update
helm install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace --set installCRDs=true
Verify installation:
kubectl get pods -n cert-manager
Step 2: Configure a Certificate Authority (CA)
First, we need a CA to issue certificates for our services.
Create a self-signed root CA:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: ca-issuer
spec:
selfSigned: {}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: root-ca
namespace: cert-manager
spec:
secretName: root-ca-secret
isCA: true
duration: 365d
renewBefore: 30d
subject:
organizations:
- MyOrg
commonName: root-ca
privateKey:
algorithm: RSA
size: 2048
issuerRef:
name: ca-issuer
kind: ClusterIssuer
Apply it:
kubectl apply -f ca.yaml
Step 3: Issue TLS Certificates for Services
Now, let’s create an Issuer that will generate certificates signed by our CA:
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: ca-issuer
namespace: default
spec:
ca:
secretName: root-ca-secret
Apply it:
kubectl apply -f issuer.yaml
Now, request certificates for App One and App Two:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: app-one-tls
namespace: default
spec:
secretName: app-one-tls-secret
duration: 90d
renewBefore: 2160h
issuerRef:
name: ca-issuer
kind: Issuer
dnsNames:
- app-one.default.svc.cluster.local
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: app-two-tls
namespace: default
spec:
secretName: app-two-tls-secret
duration: 90d
renewBefore: 2160h
issuerRef:
name: ca-issuer
kind: Issuer
dnsNames:
- app-two.default.svc.cluster.local
Apply it:
kubectl apply -f app-certs.yaml
Step 4: Deploy the Services with TLS
Now, let’s deploy App One and App Two, mounting the certificates.
App One Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-one
labels:
app: app-one
spec:
replicas: 1
selector:
matchLabels:
app: app-one
template:
metadata:
labels:
app: app-one
spec:
containers:
- name: app-one
image: nginx
ports:
- containerPort: 443
volumeMounts:
- name: tls
mountPath: "/etc/tls"
readOnly: true
volumes:
- name: tls
secret:
secretName: app-one-tls-secret
Apply it:
kubectl apply -f app-one.yaml
App Two Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-two
labels:
app: app-two
spec:
replicas: 1
selector:
matchLabels:
app: app-two
template:
metadata:
labels:
app: app-two
spec:
containers:
- name: app-two
image: nginx
ports:
- containerPort: 443
volumeMounts:
- name: tls
mountPath: "/etc/tls"
readOnly: true
volumes:
- name: tls
secret:
secretName: app-two-tls-secret
Apply it:
kubectl apply -f app-two.yaml
Step 5: Test mTLS Communication
We will now test service-to-service communication using mTLS.
Run a test pod with curl:
kubectl run curl-test --rm -it --image=curlimages/curl -- /bin/sh
Inside the pod, run:
curl --cacert /etc/tls/ca.crt --cert /etc/tls/tls.crt --key /etc/tls/tls.key https://app-two.default.svc.cluster.local:443
Expected Output:
Hello from App Two
If the request fails, check logs and ensure the correct ports are used.
Conclusion
With this setup, we’ve successfully implemented Mutual TLS (mTLS) in Kubernetes using Cert-Manager.
Encrypted Communication – All traffic is secured via TLS.
Mutual Authentication – Both services verify each other.
Automated Certificate Lifecycle – Cert-Manager handles issuance & renewal.
Have you implemented mTLS in your Kubernetes clusters? Share your experiences in the comments! ![]()