When an MQTT TLS handshake failed error appears, the connection dies before MQTT even starts, and most client libraries report it with an unhelpful message. This guide shows you how to isolate the cause in minutes with openssl s_client, then walks through the six problems behind nearly every failure: wrong port, missing CA, SNI, clock drift, TLS version mismatch and intercepting proxies. All tests run against broker.connectmq.com on port 8883, so you can compare a known-good endpoint with your own.
What Happens in a TLS Handshake
Before your client sends the MQTT CONNECT packet, it must complete a TLS handshake with the broker:
- The client sends ClientHello with supported TLS versions, cipher suites and the server name (SNI).
- The broker answers with its chosen version and cipher, plus its certificate chain.
- The client verifies the chain against trusted roots, checks the validity dates and matches the hostname.
- Both sides derive keys and switch to encrypted traffic.
A failure at any step closes the connection. Your MQTT library then shows something like A TLS error occurred (mosquitto), ssl.SSLError (Python), ECONNRESET or a certificate error (Node.js), or state=-2 (PubSubClient on ESP32). The underlying reason is what you need, and OpenSSL shows it directly. For the correct baseline settings, see our MQTT TLS configuration guide.
Step 1: Reproduce the Handshake With openssl s_client
Run this from the same machine or network as the failing client:
openssl s_client -connect broker.connectmq.com:8883 \
-servername broker.connectmq.com -showcerts </dev/null
A healthy result ends with lines similar to:
Verify return code: 0 (ok)
and shows the negotiated protocol and cipher. If OpenSSL succeeds but your application fails, the problem is in the application's configuration (CA path, hostname, library options). If OpenSSL fails too, the problem is the network, the trust store or the clock. That single comparison cuts the search space in half.
Useful variants:
# Short summary of protocol, cipher and verification
openssl s_client -connect broker.connectmq.com:8883 -servername broker.connectmq.com -brief </dev/null
# Verify against a specific CA file, as an embedded device would
openssl s_client -connect broker.connectmq.com:8883 -servername broker.connectmq.com \
-CAfile isrgrootx1.pem </dev/null
# Inspect certificate dates and names
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
MQTT TLS Handshake Failed: Symptoms and Causes
| Symptom | Likely cause | Fix |
|---|---|---|
wrong version number or packet length too long |
TLS client talking to a plaintext port (1883) | Use port 8883 for TLS |
| Connection hangs, then times out | Plain MQTT client on port 8883, or port blocked by firewall | Enable TLS in the client; test reachability |
unable to get local issuer certificate (code 20) |
Client does not trust the root CA | Install or point to the correct CA bundle |
self-signed certificate in certificate chain (code 19) |
Intercepting proxy or antivirus replaces the certificate | Bypass inspection or trust the proxy CA deliberately |
certificate is not yet valid (code 9) |
Device clock is behind | Sync time with NTP before connecting |
certificate has expired (code 10) |
Clock is ahead, or server certificate really expired | Check the device clock and the server dates |
Hostname mismatch / IP address mismatch |
Connecting by IP or wrong name | Connect by the hostname on the certificate |
no protocols available / unsupported protocol |
Client limited to TLS versions the server does not offer | Allow TLS 1.2 or newer in the client |
alert handshake failure |
No shared cipher suite or missing SNI | Update the TLS library; send SNI |
Cause 1: Wrong Port or Scheme
This is the most common mistake. Port 1883 speaks plain MQTT; port 8883 expects TLS from the first byte. If your client starts TLS against 1883, OpenSSL reports wrong version number because the server's reply is not a TLS record. If a plaintext client connects to 8883, the broker waits for a ClientHello that never comes and the connection eventually closes.
Check your URL scheme too. In MQTT.js, mqtt:// means plaintext and mqtts:// means TLS; in browsers, use wss://broker.connectmq.com:8084/mqtt. In Python, calling tls_set() is what enables TLS; changing the port alone is not enough.
To check whether the port is reachable at all:
# Linux/macOS
nc -vz broker.connectmq.com 8883
# Windows PowerShell
Test-NetConnection broker.connectmq.com -Port 8883
Corporate and guest networks often block 8883. If it is blocked, secure WebSocket on 8084 is a common workaround; see MQTT over WebSocket.
Cause 2: Missing or Wrong CA Certificate
Verification error 20 means the client could not build a chain to a trusted root. Typical situations:
- mosquitto clients do not use the system store unless you pass
--capath /etc/ssl/certs(Linux) or--cafile. On Windows, download the ISRG Root X1 PEM and pass--cafile. - Minimal Docker images often lack the
ca-certificatespackage. - Embedded devices were given the wrong root, a truncated PEM, or the leaf certificate instead of the root.
- Python on macOS installed from python.org needs its certificate installer run once; details are in SSL CERTIFICATE_VERIFY_FAILED in MQTT.
The broker certificate for ConnectMQ is issued by Let's Encrypt, so any up-to-date system trust store already includes the required root.
Cause 3: Missing SNI
Server Name Indication tells the server which hostname you want. Load-balanced and multi-tenant endpoints may present a default certificate, or reject the handshake, when SNI is missing. Most libraries send SNI automatically when you connect with a hostname. Problems appear when code resolves the IP address first and connects to the IP, or when very old TLS stacks are used. Always pass the hostname to the client library, and in OpenSSL tests always include -servername.
Cause 4: Clock Drift
Certificates are valid only between two dates. A Raspberry Pi without network time, an ESP32 that has not finished NTP, or a VM restored from an old snapshot can be days or decades off. Check with date -u on Linux, and on microcontrollers wait for NTP before opening the TLS connection. The ESP32 certificate pinning guide includes a ready-made NTP wait loop.
Cause 5: TLS Version or Cipher Mismatch
Old firmware, legacy Java runtimes or hard-coded settings such as Python's ssl.PROTOCOL_TLSv1 can offer only versions that modern servers have disabled. Test which versions a server accepts:
openssl s_client -connect broker.connectmq.com:8883 -servername broker.connectmq.com -tls1_2 </dev/null
openssl s_client -connect broker.connectmq.com:8883 -servername broker.connectmq.com -tls1_3 </dev/null
Configure your client to negotiate automatically (for example ssl.PROTOCOL_TLS_CLIENT in Python) rather than pinning a single version. Our article on MQTT over TLS 1.3 explains version and cipher choices in more depth.
Cause 6: TLS Inspection and Proxies
Some corporate firewalls and antivirus products intercept TLS by presenting their own certificate. The symptom is verification error 19 and an issuer name belonging to the security product. Ask your network team to exempt the broker hostname, or, if inspection is mandatory, explicitly trust the organisation's CA in that environment only. Never ship devices with verification disabled to work around a proxy.
Systematic Checklist
- Run
openssl s_clientwith-servernamefrom the failing environment. - Confirm the port matches the transport: 1883 plain, 8883 TLS, 8084 secure WebSocket.
- Confirm the client connects by hostname, not IP.
- Check the trust store or CA file contains the right root.
- Check the system clock in UTC.
- Allow automatic TLS version negotiation.
- Rule out proxies by testing from a different network, such as a phone hotspot.
- Only after the handshake succeeds, debug MQTT-level errors such as bad credentials or a client ID conflict.
The last point matters: a handshake that succeeds followed by a disconnect with a CONNACK reason code is an authentication or protocol problem, not TLS.
Fewer Handshake Problems With a Managed Broker
Many TLS incidents on self-hosted brokers come from the server side: expired certificates, incomplete chains or a listener misconfigured after an upgrade. A fully managed MQTT service moves that responsibility to the provider. ConnectMQ serves TLS on 8883 and secure WebSocket on 8084 with automatically renewed Let's Encrypt certificates, so if a handshake fails, you know to look at the client side. You can test any client against the public MQTT test broker first, and our cloud MQTT broker guide covers how hosted brokers fit into production.
Get Started with ConnectMQ
Create a free broker at app.connectmq.com on the Serverless plan with no credit card, copy the generated credentials, and use the in-browser MQTT client in the portal to confirm the broker works before debugging your own device code.
Frequently Asked Questions
Why does mosquitto_sub only say "A TLS error occurred"?
The mosquitto clients hide the OpenSSL detail. Run openssl s_client with the same host, port and CA file to see the exact verification error.
Can a wrong username or password cause a TLS handshake failure?
No. Credentials are checked after the handshake, inside the MQTT CONNECT exchange. A credential problem appears as a CONNACK refusal, not a TLS error.
My client works on Wi-Fi at home but not at the office. Why?
The office network probably blocks port 8883 or inspects TLS traffic. Test with Test-NetConnection or nc, and try secure WebSocket on port 8084.
Should I disable certificate verification to get connected?
No. Disabling verification hides the real problem and removes protection against impostor brokers. Fix the CA, hostname or clock instead.



