TLS is a Handshake, mTLS is a Hug. With zero Trust — no room for Thug. by Sahil Khan on September 29, 2025 68 views

In today’s distributed systems — especially microservices architectures — securing internal service-to-service communication is just as critical as securing public-facing APIs.
This blog will walk you through what mTLS is, why it’s needed, and how to implement it with simple step-by-step commands and Spring Boot examples.
💡 Why Secure Inter-Service Communication?
We are deploying a new user-service in production. It talks to auth-service, payment-service, and more.
These services might be running:
- On different machines
- Inside containers or virtual machines
And they all talk to each other through internal APIs — over the network.
❗ The Problem
Without secure communication:
- Anyone on the network could pretend to be a trusted service and send fake requests.
- Data in transit could be read or modified by attackers.
- Bugs or weak points in internal services could be exposed to attackers
🔒 Common Security Approaches
Approach | Pros | Cons |
---|---|---|
🔑 JWT Tokens | Auth + fine-grained control | Extra processing, tokens must be passed and validated |
📍 IP Whitelisting | Simple to set up | Breaks with scaling, dynamic IPs, or cloud setups |
🤝 mTLS | Strong identity & encryption | Slightly more complex to configure |
🔐 A Quick Intro to How TLS Works
Before we dive into mTLS, let’s first understand TLS (Transport Layer Security) — the protocol that keeps our web browsing safe over HTTPS.
🧠 Think of TLS as:
“Let’s make sure I’m talking to the right server, and let’s keep everything we say private.”
✨ What Happens During a TLS Handshake?
Here’s a simplified version of the steps:
- Client (e.g., browser) says: “Hey server, let’s talk securely. Here’s what I support (TLS versions, encryption algorithms, etc.).”
- Server responds: “Sounds good. Here’s my certificate — proof that I’m who I say I am.”
- Client verifies the server’s certificate:
- Ensures it’s issued by a trusted Certificate Authority (CA)
- Checks that it’s valid (not expired or tampered with)
- They agree on encryption keys:
- They perform a secure key exchange
- All future communication is encrypted using this shared key
- Secure channel is now established : All future messages are encrypted.
🔐 TLS Guarantees Two Things:
Feature | Description |
---|---|
🧾 Authentication | Verifies the server’s identity |
🛡️ Encryption | Protects data in transit from eavesdropping or tampering |
🤝 What is mTLS (Mutual TLS)?
So we’ve got TLS down — time to kick it up a notch with mutual TLS.
Mutual TLS (mTLS) is just like TLS — but both the client and the server present certificates and verify each other’s identity.
✅ TLS = Only the server proves its identity
🔁 mTLS = Both sides prove who they are
This ensures that only authorized clients can connect to a service — and vice versa — which is crucial in distributed systems where services talk to each other.
🔐 Where Does mTLS Fit?
mTLS is a key part of Zero Trust security, which is based on a simple idea:
“Never trust, always verify.”
In the old days, we assumed anything inside our network was safe. But in modern cloud-native systems, that assumption just doesn’t hold. Even internal services should prove they are who they claim to be.
🧠 In Microservices
With multiple services talking to each other, mTLS helps ensure:
- Only trusted services can connect to each other
- Data is encrypted in transit
- No one can impersonate another service without a valid certificate
✅ Why mTLS?
- Prevents spoofed service calls
- Eliminates reliance on IP whitelisting
- Aligns perfectly with cloud-native and zero-trust principles
Service A (Client) Service B (Server)
─────────────────── ─────────────────────
| |
| ClientHello(TLS version,ciphers,etc) |
|────────────────────────────────────────>|
| |
| ServerHello + Server Certificate |
| (Signed by Root CA) |
|<────────────────────────────────────────|
| |
Verify Server Certificate |
| |
| Client Certificate(Signed by RootCA) |
|────────────────────────────────────────>|
| |
| Verify Client Certificate
| |
Secure Key Exchange (TLS handshake complete)
|<───────────────────────────────────────>|
| |
✅ Encrypted, Authenticated Channel Established ✅
|<───────────────────────────────────────>|
🧪 Step-by-Step mTLS Setup with Spring Boot
We’ll walk through how to:
- Create a Root CA
→ Acts as the “authority” that signs and trusts all service certificates. - Generate service certificates
→ Each service gets its own certificate, signed by the Root CA to prove its identity. - Convert to PKCS12 format
→ A secure and portable format that combines the certificate and private key for use in Java/Spring. - Create the truststore
→ This is how a service decides who it trusts (i.e., which certs or CAs to accept).
🔧 Step 1: Create a Root Certificate Authority (CA)
openssl genrsa -out rootCA.key 2048
openssl req -x509 -new -nodes -key rootCA.key \
-sha256 -days 1024 -out rootCA.crt \
-subj "/C=IN/ST=GJ/L=Gandhinagar/O=Argusoft/OU=Backend/CN=rootCA"
📌 Subject Fields Explained:
Field | Meaning |
---|---|
C | Country |
ST | State |
L | Locality/City |
O | Organization |
OU | Organizational Unit |
CN | Common Name (usually service domain) |
Typically, the
CN
field is used to specify the domain name of the service (e.g.,mysite.com
). But in microservices — especially with service discovery tools like Eureka — services often communicate using logical service names likeauth-service
.
🔧 Step 2: Create Key and CSR for a Service
# Private Key
openssl genrsa -out auth-service.key 2048
# CSR (Certificate Signing Request)
openssl req -new -key auth-service.key \
-out auth-service.csr \
-subj "/C=IN/ST=GJ/L=Gandhinagar/O=Argusoft/OU=AuthService/CN=auth-service"
# Sign with Root CA
openssl x509 -req -in auth-service.csr \
-CA rootCA.crt -CAkey rootCA.key -CAcreateserial \
-out auth-service.crt -days 500 -sha256
📝 -CAcreateserial creates a rootCA.srl file to track cert serials.
🔄 Step 3: Convert to PKCS12 for Spring Boot
openssl pkcs12 -export \
-in auth-service.crt \
-inkey auth-service.key \
-out auth-service.p12 \
-name auth-service \
-CAfile rootCA.crt -caname rootCA \
-password pass:yourpassword
The
auth-service.p12
file combines the service’s private key and certificate in PKCS#12 format, serving as the keystore that proves your service’s identity in mTLS.
🔐 Step 4: Create the Truststore
keytool -importcert \
-alias rootCA \
-file rootCA.crt \
-keystore truststore.p12 \
-storetype PKCS12 \
-storepass yourpassword -noprompt
This truststore lets your service verify who it’s talking to — it’s how you decide who gets through the door.
🔍 Optional: Service-Specific Truststore
If you want more control — for example, which specific services one service trusts — you can import only the individual service certificate(s) instead of the entire CA:
keytool -importcert \
-alias auth-service \
-file auth-service.crt \
-keystore custom-truststore.p12 \
-storetype PKCS12 \
-storepass yourpassword -noprompt
✅ This means:
- The service will only trust exactly those certificates.
- You can have different truststores per service, limiting trust more precisely.
🧠 This is useful when you’re dealing with sensitive services, want to control exactly who talks to whom, or aren’t using a shared CA across all services.
🧠 When to use each trust approach
Approach | Trust Scope | Benefit |
---|---|---|
Root CA truststore | Trusts all certificates signed by the CA | Simple, scalable with many services |
Service-specific truststore | Trusts only selected certs/service | Fine-grained control per consumer |
🚀 Configure Spring Boot for mTLS
✅ For All Spring Boot Apps (server-side)
server.ssl.key-store=classpath:auth-service.p12
server.ssl.key-store-password=yourpassword
server.ssl.key-store-type=PKCS12
server.ssl.trust-store=classpath:truststore.p12
server.ssl.trust-store-password=yourpassword
server.ssl.trust-store-type=PKCS12
# Require clients to present valid cert
server.ssl.client-auth=need
🔄 Eureka-Specific Setup (When service registers to Eureka over HTTPS)
Only use this block when your service is acting as a Eureka client (not for general mTLS).
eureka.client.tls.enabled=true
eureka.client.tls.key-store=classpath:auth-service.p12
eureka.client.tls.key-store-password=yourpassword
eureka.client.tls.key-password=yourpassword
eureka.client.tls.trust-store=classpath:truststore.p12
eureka.client.tls.trust-store-password=yourpassword
📌 Eureka uses an internal Jersey HTTP client. It needs its own key & trust store to connect to Eureka server over HTTPS.
🧱 Practical Alternatives to Manual mTLS
Even though manual mTLS is powerful, it can be tricky to scale and manage in large environments. Here are some popular alternatives:
✅ JWT (JSON Web Tokens)
Use when services need fine-grained identity and authorization, like multi-tenant user scopes.
- Tokens are signed and verified
- Good for user- or app-level permissions
- Needs token issuance + validation logic
Great for identity and access control — like a signed pass that skips the handshake.
🌐 NGINX / Envoy Proxy
Use a sidecar proxy to handle TLS/mTLS for your service.
- Offloads TLS from your app code
- Your service talks plain HTTP locally
- Proxy handles encryption + identity verification externally
Works great if you want TLS without touching your app internals.
🛡️ Istio (Service Mesh on Kubernetes)
A powerful alternative when using Kubernetes:
- Automatically handles mTLS between services
- Injects a small sidecar (Envoy) next to each service
- Handles all the encryption and certificate rotation
- Adds tracing, retries, policies, and more — out of the box
Perfect for large scale projects and if you’re already deep into Kubernetes.
💭 Final Thoughts
mTLS might seem intimidating at first — but once you understand how it works, it’s just TLS with ID checks on both sides. It scales better than IP lists, aligns with Zero Trust, and keeps internal traffic as secure as public APIs. It’s a tiny shift, but it makes a big difference. Because when services talk behind the scenes, they still deserve encryption — and maybe even a little hug. 🤗