gRPC Mutual TLS
Standard TLS proves the identity of the server. The client checks a certificate and knows it talks to the real server. Mutual TLS, often written mTLS, adds the reverse check. The server also demands a certificate from the client. Both sides prove who they are before any message moves.
The Office Badge Analogy
A visitor shows a badge at the front desk of a secure office. The visitor also expects the receptionist to wear a company badge. Both people verify each other. Regular TLS checks only the receptionist's badge. Mutual TLS checks both.
Regular TLS Mutual TLS
Client ──"Show your cert"──▶ Server Client ◀──"Show your cert"──▶ Server
Client ◀──server cert─────── Server Client ◀──server cert──────── Server
Client ───client cert───────▶ Server
Only server verified Both sides verified
When to Use mTLS
- Service to service calls inside a private network
- Banking, healthcare, and other regulated systems
- Zero trust designs, where no network location earns automatic trust
- Calls from known partner systems
Certificate Pieces
| File | Role |
|---|---|
| ca.crt | Certificate authority that both sides trust |
| server.crt and server.key | Server identity |
| client.crt and client.key | Client identity |
Create Test Certificates
These commands build a small certificate authority for local learning. Production systems use a managed certificate authority.
openssl genrsa -out ca.key 4096
openssl req -x509 -new -key ca.key -sha256 -days 365 -subj "/CN=Demo CA" -out ca.crt
printf "subjectAltName=DNS:localhost" > san.ext
openssl genrsa -out server.key 2048
openssl req -new -key server.key -subj "/CN=localhost" -out server.csr
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-days 90 -sha256 -extfile san.ext -out server.crt
openssl genrsa -out client.key 2048
openssl req -new -key client.key -subj "/CN=orders-service" -out client.csr
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-days 90 -sha256 -out client.crt
Go requires a subject alternative name on server certificates. The san.ext file supplies it.
Server Setup in Go
cert, _ := tls.LoadX509KeyPair("server.crt", "server.key")
caPEM, _ := os.ReadFile("ca.crt")
pool := x509.NewCertPool()
pool.AppendCertsFromPEM(caPEM)
creds := credentials.NewTLS(&tls.Config{
Certificates: []tls.Certificate{cert},
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: pool,
MinVersion: tls.VersionTLS13,
})
server := grpc.NewServer(grpc.Creds(creds))
The setting RequireAndVerifyClientCert rejects every client without a valid certificate signed by the trusted authority.
Client Setup in Go
cert, _ := tls.LoadX509KeyPair("client.crt", "client.key")
caPEM, _ := os.ReadFile("ca.crt")
pool := x509.NewCertPool()
pool.AppendCertsFromPEM(caPEM)
creds := credentials.NewTLS(&tls.Config{
Certificates: []tls.Certificate{cert},
RootCAs: pool,
ServerName: "localhost",
})
conn, err := grpc.NewClient("localhost:50051", grpc.WithTransportCredentials(creds))
Read the Caller Identity
The verified certificate tells the server exactly who is calling. Handlers read the name and make decisions.
p, ok := peer.FromContext(ctx)
if ok {
tlsInfo := p.AuthInfo.(credentials.TLSInfo)
caller := tlsInfo.State.PeerCertificates[0].Subject.CommonName
log.Printf("call from %s", caller)
}
Certificate Rotation
Certificates expire. Short lifetimes, measured in days or hours, limit damage from a leaked key. Automate renewal with tools such as cert-manager or a service mesh. Reload certificates without a restart so that rotation causes no downtime.
Troubleshooting
| Symptom | Likely Cause |
|---|---|
| bad certificate error | Client certificate signed by an untrusted authority |
| certificate has expired | Expired file on either side |
| x509 SAN error | Server name missing from the certificate |
| Connection resets at handshake | Client sent no certificate |
Summary
Mutual TLS verifies both sides of a connection. Create a certificate authority, issue certificates to servers and clients, require client verification on the server, and rotate certificates often. Strong identity at the connection level supports every higher security layer.
