NATS TLS mutual certificate authentication isn’t about encrypting data in transit, it’s about proving identity to NATS itself.

Let’s see it in action. Imagine two NATS clients, client-a and client-b, and a NATS server, server-main. Both clients and the server have their own certificates and private keys. When client-a connects to server-main, the server will ask client-a to prove its identity by presenting a certificate. client-a will then send its certificate, and server-main will verify it against a list of trusted client certificates it has. If the verification passes, the connection is established. The same process happens when client-b connects.

Here’s a typical NATS server configuration for mutual TLS:

listen: ":4222"
tls:
  cert_file: "/etc/nats/certs/server.pem"
  key_file: "/etc/nats/certs/server.key"
  client_auth: true
  ca_file: "/etc/nats/certs/clients.pem"

The tls block enables TLS. cert_file and key_file point to the server’s own certificate and private key. The crucial part for mutual authentication is client_auth: true. This tells the server to require clients to present a certificate. ca_file is where the server keeps the certificates of the clients it trusts. This file should contain the Certificate Authority (CA) certificate that signed the client certificates, or the client certificates themselves if they are self-signed or signed by different CAs.

For the clients, the configuration looks like this:

servers: ["tls://server-main:4222"]
tls:
  cert_file: "/etc/nats/certs/client-a.pem"
  key_file: "/etc/nats/certs/client-a.key"
  ca_file: "/etc/nats/certs/server.pem"

Here, servers specifies the TLS-enabled NATS server. cert_file and key_file are the client’s own certificate and private key. ca_file points to the CA certificate that signed the server’s certificate, allowing the client to verify the server’s identity.

The problem this solves is granular access control and enhanced security beyond just network-level encryption. With mutual TLS, you’re not just encrypting traffic; you’re ensuring that only authenticated and authorized NATS clients and servers can communicate. This is essential for distributed systems where different services need to talk to each other securely and where you want to prevent rogue clients from joining the NATS cluster.

Internally, when client_auth is true, the NATS server initiates a TLS handshake. After the server presents its certificate, it requests a certificate from the client. The client then sends its certificate. The server uses the ca_file to verify that the client’s certificate is trusted. This verification involves checking the certificate’s signature against the CA certificate in ca_file and ensuring the certificate hasn’t expired. If successful, the handshake continues, and the encrypted connection is established.

The ca_file on the server can contain multiple CA certificates or individual client certificates. If it contains CA certificates, the server will trust any client certificate signed by any of those CAs. If it contains individual client certificates, only those specific clients will be trusted. For self-signed certificates, you’d typically concatenate the client’s public certificate into the server’s ca_file.

When a client connects to a TLS-enabled NATS server that requires client authentication, the client’s tls.cert_file and tls.key_file are used to present its identity. The client’s tls.ca_file is used to verify the server’s certificate, ensuring the client is talking to the legitimate NATS server and not an imposter.

The most subtle point is that the ca_file on the client side is only for verifying the server’s identity, while the ca_file on the server side is only for verifying the clients’ identities. They serve distinct, albeit related, purposes in the mutual authentication dance.

If you’ve correctly set up mutual TLS for all your clients and servers, the next security consideration will be managing certificate rotation and revocation.

Want structured learning?

Take the full Nats course →