The most surprising thing about MQTT certificate authentication is that it doesn’t actually authenticate the device itself, but rather the connection the device is making.

Let’s see this in action. Imagine a simple MQTT broker running on localhost:8883. We’ll create a client that connects using a certificate.

First, we need a CA certificate, a server certificate/key pair, and a client certificate/key pair. For this example, let’s assume we have ca.crt, server.crt, server.key, client.crt, and client.key.

Here’s how a client using mosquitto_pub might connect:

mosquitto_pub \
  --cafile ca.crt \
  --cert client.crt \
  --key client.key \
  -h localhost \
  -p 8883 \
  -t "device/status" \
  -m "online"

When this command runs, the broker (mosquitto in this case) will present its server.crt. The client then verifies that server.crt was signed by the ca.crt provided via --cafile. Crucially, the client also presents its client.crt to the broker. The broker then verifies that client.crt was signed by the same ca.crt. If both checks pass, the TLS handshake completes, and the connection is established. The broker can then use information from client.crt (like the Common Name or Subject Alternative Name) to identify the "client" for authorization purposes.

This process is Mutual TLS (mTLS). It ensures that both the client and the server trust each other’s identity based on certificates issued by a common Certificate Authority (CA). The primary problem it solves is preventing unauthorized clients from connecting to the broker and eavesdropping on or publishing messages to topics they shouldn’t. It also prevents unauthorized servers from impersonating the legitimate broker, protecting clients from connecting to malicious endpoints.

Internally, the flow is:

  1. Client initiates a TLS connection to the broker.
  2. Broker sends its certificate to the client.
  3. Client verifies the broker’s certificate against its trusted CA.
  4. Broker requests a client certificate from the client.
  5. Client sends its certificate to the broker.
  6. Broker verifies the client’s certificate against its trusted CA.
  7. If both verifications succeed, the TLS tunnel is established.

The exact levers you control are the certificates themselves and how they are configured on both the broker and the clients. On the broker side (e.g., mosquitto.conf), you’d have directives like:

# Enable TLS
listener 8883
protocol mqtts

# Path to the CA certificate used to verify client certificates
cafile /path/to/ca.crt

# Path to the server certificate
certfile /path/to/server.crt

# Path to the server private key
keyfile /path/to/server.key

# Require clients to present a certificate
# (Optional, but common for mTLS)
require_certificate true

On the client side, it’s passed via command-line arguments as shown earlier, or configured within client libraries. The key is that the --cafile on the client verifies the broker’s identity, and the --cert and --key on the client are what the broker verifies.

The one thing most people don’t grasp is that the ca.crt specified on the client is for verifying the server, while the ca.crt specified on the server is for verifying the clients. If you use a single CA for both, it’s easy to confuse which file is doing what. A common mistake is to use the server’s CA file on the client, or vice-versa, leading to handshake failures because the wrong trust anchor is being used.

The next hurdle after successfully implementing mTLS is often the authorization phase, where you map the authenticated client identity (derived from its certificate) to specific topic access permissions.

Want structured learning?

Take the full Mqtt course →