Dev.to · 7 min read

Why Your MQTT TLS Connection Fails, Stage by Stage

Why Your MQTT TLS Connection Fails, Stage by Stage

I build MQTT Commander, an MQTT client for iPhone and iPad, so I read a lot of other people's failed TLS handshakes. The same half-dozen causes come up every time, and you can pin most of them down before you touch the broker config. Plain MQTT on port 1883 either connects or it doesn't. Add TLS and five more things sit between you and a working session: the certificate chain, hostname verification, ALPN, client certificates and broker-side authorization. The MQTT client that reports the failure sees a closed socket and little else. Three layers, in order Before the first MQTT packet moves, three things have to succeed one after another. TCP. The client resolves the broker hostname, opens a socket on 8883 (MQTT over TLS) or 443 / 8084 (MQTT over WebSocket and TLS), and completes the handshake. A firewall, a wrong port or a broker that isn't running fails here, and the fix is on the network side even when the error text mentions TLS. On iOS a LAN broker that never answers has one more possible cause: the app has no Local Network permission in Settings. TLS handshake. Client and server agree on a cipher suite, exchange certificates and set up the encrypted channel, and most secure-MQTT failures land somewhere in that exchange. MQTT CONNECT. Only now does the protocol itself start. The client sends CONNECT, the broker answers with CONNACK. A non-zero reason code at this point means the broker refused you on purpose: bad credentials, a client ID it won't accept, an ACL that denies you. TLS finished before any of that. Find out which of the three failed before you change anything. Where TLS breaks Most MQTT clients collapse a TLS failure into "connection failed". To see the real verdict, ask OpenSSL: openssl s_client -connect broker.example.com:8883 \ -servername broker.example.com -verify_hostname broker.example.com The verify error lines below are what it prints for each cause. Other TLS stacks word them differently, but the categories are the same. An expired or incomplete chain Every certificate in the chain expires, intermediates included. An expired intermediate breaks the connection while the leaf certificate is still valid, and the leaf is the one you check by hand. verify error:num=10:certificate has expired verify error:num=20:unable to get local issuer certificate verify error:num=21:unable to verify the first certificate Codes 20 and 21 mean the chain stops short of a root you trust. Either the broker sends only its leaf certificate and skips the intermediate, or the root is a private CA you haven't imported yet (next section). The first case you fix on the broker: every other client hits the same wall. Code 9, certificate is not yet valid, usually means the device clock is wrong rather than the certificate. A private CA the device doesn't trust Mosquitto on a Raspberry Pi, an internal staging broker, an AWS IoT custom endpoint: a private CA signs all of these, and no phone trusts that CA out of the box. verify error:num=19:self-signed certificate in certificate chain verify error:num=18:self-signed certificate Code 19 is a private root; code 18 is a broker that serves a self-signed leaf with no CA at all. Import the root as PEM (for code 18, the broker's certificate is its own root) and compare its SHA-256 fingerprint with the one your CA published before you trust it. Whoever holds the key of an unverified root can impersonate your broker. The hostname isn't in the certificate TLS clients check that the hostname you connected to appears in the certificate's SAN (Subject Alternative Name); a name that only appears in CN no longer counts on iOS. The match is exact, so broker.example.com and mqtt.example.com are different names to TLS even when they point at the same box. verify error:num=62:hostname mismatch Connect using a name that is in the SAN. If you have to reach the broker by an address that isn't in the certificate, send the right server name during the handshake with an SNI override instead of editing your hosts file. Missing or wrong ALPN Some brokers multiplex MQTT onto a shared port and use ALPN to route it. AWS IoT Core takes MQTT with certificate authentication on port 443 only when the client advertises x-amzn-mqtt-ca. Leave it out and the handshake dies with a generic alert, or the socket closes right after it, and nothing in the message says ALPN. One common form of it: error:0A000438:SSL routines::tlsv1 alert internal error Standard MQTT over TLS on 8883 needs no ALPN entry in most setups. On 443, ALPN is the first thing to check. The AWS IoT guide on my site lists the exact values for that endpoint. A TLS version the client won't negotiate iOS negotiates TLS 1.2 or 1.3 and refuses 1.0 and 1.1. A broker built on an old library that tops out at 1.1, which still happens on legacy gateways and old embedded images, produces: error:0A000102:SSL routines:ssl_choose_client_version:unsupported protocol The client is refusing what the broker offered, so the fix is on the broker. Mutual TLS Standard TLS authenticates the broker to you. mTLS authenticates you back, which is why AWS IoT device provisioning and industrial fleets use it: each device carries its own certificate instead of a shared password. You need three things: a client certificate, its private key, and the CA that signed both ends. They arrive as a passphrase-protected .p12 / .pfx bundle, or as separate PEM files. Three failures cover most mTLS trouble. The passphrase is wrong. Import fails before anything reaches the network, and OpenSSL calls it mac verify failure. Passphrases are case-sensitive. If you have lost it, re-export the bundle from the CA rather than guessing. The certificate and key don't match. Import the client identity as a single .p12 bundle and the pair stays together by construction. With separate PEM files, a key from the wrong export surfaces as: key values mismatch The handshake succeeds and the broker hangs up anyway. The broker accepted your certificate and then refused the session on authorization grounds. In AWS IoT Core, look for a policy that doesn't grant iot:Connect for the client ID you present. With MQTT 5 the CONNACK reason code says so: 0x87, not authorized. With MQTT 3.1.1 the broker often closes the socket without a CONNACK at all. Reading CONNACK If TLS completed and the session still dies, the reason code names the cause: MQTT 5 MQTT 3.1.1 Meaning 0x87 5 Not authorized: an ACL or IoT policy denies Connect 0x86 4 Bad username or password 0x85 2 Client identifier not valid These three cover most post-handshake refusals. The checklist Port matches the transport: 8883 for MQTT over TLS, 8084 for MQTT over WSS, 443 where ALPN does the routing Chain is complete and nothing in it has expired, intermediates included Private CA root is imported, and you verified its fingerprint before trusting it Connection hostname appears in the certificate's SAN ALPN is set if the broker needs it, such as x-amzn-mqtt-ca for AWS IoT on 443 For mTLS: certificate, key and CA all present, passphrase correct Client key matches the certificate's public key Broker policy grants Connect for this client ID Work down it in order and stop at the first item that fails. You can't test anything below it until you fix that one. The site version of this checklist sits next to a table of what each diagnostic stage looks like when it fails. On iOS I do this with MQTT Commander. Its Connection Doctor runs the connection stage by stage, from DNS and TCP through the TLS handshake, chain, expiry, hostname and ALPN/SNI to CONNACK, authentication and a write test against the broker ACL, stops at the first stage that fails and says what to change. The free download shows the transport and MQTT stages plus whichever stage fails, TLS included; the one-time Pro unlock lists each TLS check on its own line and adds a shareable report that carries stage names and statuses only, with no hosts, topics or credentials. The Certificate Manager is free on every tier: it imports a CA as PEM and a client identity as .p12 or .pfx, shows the SHA-256 fingerprint, and flags an expired certificate before you connect.

This is a summary aggregated from Dev.to. Read the complete article on the original site:

Read full article at Dev.to

More Cybersecurity News