Broker Testing

How to Test an MQTT TLS Connection (openssl, mosquitto, code)

Test MQTT over TLS on port 8883: openssl s_client checks, certificate chain, SNI and hostname verification, mosquitto --cafile and fixes for common errors.

Updated · 9 min read

How to Test an MQTT TLS Connection (openssl, mosquitto, code)

To test an MQTT TLS connection, check three layers in order: the TLS handshake itself with openssl s_client, then a real MQTT session with mosquitto_sub or mosquitto_pub, then the client library your devices use. Each layer can fail for different reasons, and testing them separately tells you exactly which one is broken.

All examples use broker.connectmq.com on port 8883 (MQTT over TLS). Replace the host with your own broker.

Step 1: check the handshake with openssl

openssl s_client speaks TLS but not MQTT, which is exactly what you want: it shows the certificate the broker sends and whether your machine trusts it, without any MQTT library in the way.

Basic TLS check
openssl s_client -connect broker.connectmq.com:8883 \
  -servername broker.connectmq.com \
  -verify_hostname broker.connectmq.com \
  -showcerts </dev/null

What each option does:

  • -servername sends SNI (Server Name Indication). Brokers and load balancers that host several names use it to pick the certificate. Without it you may get a default certificate and a hostname mismatch.
  • -verify_hostname makes openssl check that the certificate is valid for that name, the same check your MQTT client does.
  • -showcerts prints every certificate the server sends, so you can see whether the intermediate is included.
  • </dev/null closes the connection right after the handshake instead of waiting for input.

At the end of the output, look for:

Healthy result
Verify return code: 0 (ok)

Anything else is a problem you will also see in your MQTT client:

openssl resultMeaning
Verify return code: 20 (unable to get local issuer certificate)The CA (or a missing intermediate) is not in your trust store
Verify return code: 18 (self-signed certificate)Server certificate is self-signed; pass its CA with -CAfile
Verify return code: 19 (self-signed certificate in certificate chain)Chain ends in a private root you have not trusted
Verify return code: 10 (certificate has expired)Renew the certificate on the broker
Verify return code: 62 (hostname mismatch)The certificate does not list the name you connected to
wrong version numberThat port is plain MQTT, not TLS (often 1883)
connect: Connection refused / timeoutPort closed or filtered — not a TLS problem

For a broker with a private CA, add -CAfile ca.crt and run the command again. If it then returns 0 (ok), your certificates are fine and the client only needs the same CA file.

Inspect the certificate quickly

Subject, issuer, SAN and expiry
openssl s_client -connect broker.connectmq.com:8883 \
  -servername broker.connectmq.com </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -issuer -dates -ext subjectAltName

Check that subjectAltName contains the exact hostname your devices use. Modern clients ignore the certificate's Common Name, so a name that only appears in CN does not count. For a CI check that fails two weeks before expiry, pipe the certificate into openssl x509 -noout -checkend 1209600.

To test which TLS versions the broker accepts, repeat the first command with -tls1_2 or -tls1_3. A device with an old TLS stack that only speaks TLS 1.2 needs the broker to keep 1.2 enabled — see TLS 1.2 vs 1.3 for MQTT.

Step 2: run a real MQTT session over TLS

A good handshake does not prove the MQTT listener works behind it. Subscribe and publish over 8883:

Terminal 1 — subscribe over TLS
mosquitto_sub -h broker.connectmq.com -p 8883 \
  --cafile /etc/ssl/certs/ca-certificates.crt \
  -u "$MQTT_USER" -P "$MQTT_PASS" \
  -t testmqtt/tls/check -v -d
Terminal 2 — publish over TLS
mosquitto_pub -h broker.connectmq.com -p 8883 \
  --cafile /etc/ssl/certs/ca-certificates.crt \
  -u "$MQTT_USER" -P "$MQTT_PASS" \
  -t testmqtt/tls/check -m "hello over tls"

mosquitto clients only enable TLS when you give them a CA option. Use --cafile with your CA bundle (the path above is Debian/Ubuntu; on macOS with Homebrew it is under $(brew --prefix)/etc/ca-certificates/cert.pem), --capath /etc/ssl/certs, or --tls-use-os-certs on mosquitto 2.x. For a private CA, point --cafile at your ca.crt. For mutual TLS add --cert client.crt --key client.key — see client certificates and mTLS.

When it fails, mosquitto is terse: it prints Error: A TLS error occurred. and nothing more. Go back to step 1 — openssl tells you why. If openssl succeeds but mosquitto fails, compare the hostname: -h must be the name in the certificate, not an IP address.

Step 3: test from your client code

Finally, test with the library your application uses. The certificate store and defaults differ per language, so a connection that works in mosquitto can still fail here.

tls_check.py (paho-mqtt 2.x)
import os
import paho.mqtt.client as mqtt

def on_connect(client, userdata, flags, reason_code, properties):
    print("CONNACK:", reason_code)
    client.publish("testmqtt/tls/check", "hello from python over tls", qos=1)

c = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, protocol=mqtt.MQTTv5)
c.tls_set()  # system CAs, hostname check on; ca_certs="ca.crt" for a private CA
c.username_pw_set(os.environ["MQTT_USER"], os.environ["MQTT_PASS"])
c.on_connect = on_connect
c.connect("broker.connectmq.com", 8883)
c.loop_forever()
tls-check.mjs (MQTT.js 5)
import mqtt from "mqtt";

const client = mqtt.connect("mqtts://broker.connectmq.com:8883", {
  username: process.env.MQTT_USER,
  password: process.env.MQTT_PASS,
  // ca: fs.readFileSync("ca.crt"),   // only for a private CA
  reconnectPeriod: 0,
});

client.on("connect", () => {
  console.log("connected over TLS");
  client.publish("testmqtt/tls/check", "hello from node over tls", () => client.end());
});
client.on("error", (err) => console.error(err.code, err.message));

The errors these produce are the ones people search for:

LibraryErrorUsual cause
Python[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificateMissing CA or intermediate
Pythoncertificate verify failed: Hostname mismatch, certificate is not valid for '...'Connected by IP or wrong name
Python[SSL: WRONG_VERSION_NUMBER] wrong version numberTLS client on a plain port
Node.jsUNABLE_TO_VERIFY_LEAF_SIGNATURE — unable to verify the first certificateServer does not send the intermediate
Node.jsSELF_SIGNED_CERT_IN_CHAINPrivate CA not passed in ca
Node.jsERR_TLS_CERT_ALTNAME_INVALID — Hostname/IP does not match certificate's altnamesName not in the SAN list

The fixes are covered one by one in certificate verify failed, TLS handshake failed and hostname mismatch. The code generator produces TLS-enabled connection snippets for other languages.

A quick TLS test checklist

  1. openssl s_client with -servername and -verify_hostname returns Verify return code: 0 (ok).
  2. -showcerts lists the server certificate and the intermediate.
  3. The SAN contains the exact hostname clients use; the certificate is not close to expiry.
  4. mosquitto_sub and mosquitto_pub exchange a message on 8883 with verification on.
  5. Your application library connects with its default trust store (or the private CA you ship).
  6. Plain port 1883 is closed or disabled in production if TLS is required.
  7. Devices with older TLS stacks connect with the TLS version and cipher suites you allow.

Want a browser-based check as well? The online MQTT client connects over secure WebSockets on 8084, which uses the same certificate in many setups — a fast way to confirm the broker's TLS side before you debug a device.

Testing TLS layer by layer turns a vague "TLS error" into a specific cause in a few minutes. Keep the openssl command and the expiry check in your scripts, and run them whenever a certificate is renewed or a load balancer changes.

Frequently asked questions

How do I check if an MQTT broker supports TLS?

Run openssl s_client -connect host:8883 -servername host. If the handshake succeeds you see the certificate chain and 'Verify return code: 0 (ok)'. A 'wrong version number' error means the port speaks plain MQTT, not TLS.

Why does TLS work in my browser but fail on my device?

Usually the server does not send its intermediate certificate. Browsers can fetch missing intermediates themselves; openssl, mosquitto and embedded TLS stacks cannot. Check that s_client -showcerts lists the intermediate, and fix the broker's certificate file.

Is it safe to use --insecure or tls_insecure_set(True)?

Only as a temporary diagnostic on your own machine. These options disable hostname verification, which makes the connection open to man-in-the-middle attacks. Fix the certificate or hostname instead and keep verification on.