Troubleshooting

MQTT Connection Errors: Causes and Fixes

Fix MQTT connection errors fast: timeouts, ECONNREFUSED, TLS failures, mixed content, CONNACK return codes, MQTT 5 reason codes and client ID reconnect loops.

Updated · 8 min read

An MQTT connection can fail at four layers: the network (DNS and TCP), the transport (TLS or WebSocket), the MQTT handshake (CONNECT / CONNACK) and the live session (keep-alive and disconnects). Identifying the layer is half the fix. This guide walks through each one, with the exact error strings and reason codes you are likely to see.

Quick triage

SymptomLayerMost likely cause
ENOTFOUND / getaddrinfo errorDNSTypo in the hostname, or a URL scheme included in the host field
Hangs, then times outNetworkFirewall dropping the port, wrong port, or TCP port used from a browser
ECONNREFUSEDNetworkNothing listening on that host and port
Certificate or handshake errorTLSUntrusted CA, hostname mismatch, or TLS against a plain port
Connection closed right after connectMQTTCONNACK with an error code — see the tables below
Connects, then drops repeatedlySessionDuplicate client ID or keep-alive misconfiguration

Connection timeouts

A timeout means your packets went out and nothing came back. The broker never got the chance to say no. Check, in order:

  1. The port matches the transport. mqtt:// on 1883, mqtts:// on 8883, wss:// on the broker's WebSocket port. See MQTT ports explained.
  2. Your network allows the port. Corporate and guest networks often block 1883 and 8883. Try wss:// on 443 to confirm.
  3. The broker is up. Public sandboxes restart without notice; test another broker to rule out your own setup.
  4. Your server firewall is open. For self-hosted brokers, the cloud security group must allow inbound traffic on the listener port.

ECONNREFUSED

ECONNREFUSED is a fast, explicit rejection from the operating system: the host is reachable, but no process is listening on that port. Typical reasons are a broker that is not running, a listener bound only to 127.0.0.1, a container port that was never published (docker run -p 1883:1883), or connecting to localhost from inside a container where the broker lives in a different one.

TLS errors

  • Self-signed or unknown CA (SELF_SIGNED_CERT_IN_CHAIN, UNABLE_TO_VERIFY_LEAF_SIGNATURE): give the client the CA file. Do not disable verification outside of local testing.
  • Hostname mismatch (ERR_TLS_CERT_ALTNAME_INVALID): connect using the name on the certificate, not an IP address or internal alias.
  • Wrong version number / packet length too long: you are speaking TLS to a plain-text port (for example mqtts:// on 1883) or plain MQTT to a TLS port.
  • Expired certificate: check the dates with openssl s_client -connect host:8883 -servername host.

Browser-only errors: mixed content and WebSocket paths

Browsers can only use MQTT over WebSockets, and they add their own rules. A page served over HTTPS cannot open ws:// — the browser blocks it as mixed content before any packet is sent, and the error only shows up in the developer console. Use wss://. The other frequent mistake is a missing path: many brokers expect /mqtt, and without it the WebSocket upgrade fails with a 404. Read more in MQTT over WebSockets.

Test a known-good wss connection

MQTT 3.1.1 CONNACK return codes

If the network and transport work, the broker answers CONNECT with CONNACK. In MQTT 3.1.1 it carries a one-byte return code:

CodeMeaningWhat to fix
0Connection acceptedNothing — you are connected
1Unacceptable protocol versionBroker does not support the requested version; switch between MQTT 3.1.1 and 5
2Identifier rejectedClient ID is invalid, too long, empty when not allowed, or blocked
3Server unavailableBroker is overloaded or in maintenance; retry with backoff
4Bad user name or passwordCheck credentials and their encoding
5Not authorizedAccount is not allowed to connect; check broker ACLs or the client ID rule

Common MQTT 5 reason codes

MQTT 5 replaces return codes with reason codes. Anything at or above 0x80 is a failure. The same codes can appear in CONNACK or in a server-sent DISCONNECT:

CodeNameTypical cause
0x80Unspecified errorGeneric failure; check broker logs
0x84Unsupported Protocol VersionBroker only speaks 3.1.1
0x85Client Identifier not validMalformed or disallowed client ID
0x86Bad User Name or PasswordWrong credentials
0x87Not authorizedValid user without permission to connect
0x88Server unavailableBroker cannot accept connections right now
0x8ABannedClient or IP blocked by the operator
0x8CBad authentication methodEnhanced auth method not supported
0x95Packet too largeCONNECT exceeds the broker's maximum packet size
0x97Quota exceededAccount hit a connection or usage limit
0x9FConnection rate exceededReconnecting too fast; add backoff

Two more show up in DISCONNECT packets during a session: 0x8D (Keep Alive timeout) and 0x8E (Session taken over). Both are covered below. For the bigger picture of what MQTT 5 changed, see MQTT 5 vs 3.1.1.

Client ID takeover loops

The spec requires a broker to disconnect an existing client when a new connection arrives with the same client ID. If two devices — or two browser tabs — share an ID and both auto-reconnect, they take turns kicking each other off every few seconds. In MQTT 5 the loser receives DISCONNECT with 0x8E; in 3.1.1 the socket simply closes.

Keep-alive disconnects

The keep-alive value in CONNECT tells the broker how often to expect traffic. If it sees nothing for one and a half times that interval, it closes the connection. Clients send PINGREQ when idle, but a blocked event loop, a sleeping device or an aggressive NAT or load balancer idle timeout can still break the connection. Keep the interval below your network's idle timeout (60 seconds is a common default) and never block the thread that services the MQTT client.

Log the real error

Most libraries hide the reason unless you listen for it. With MQTT.js:

debug.mjs
import mqtt from 'mqtt';

const client = mqtt.connect('wss://broker.emqx.io:8084/mqtt', {
  protocolVersion: 5,
  clientId: `debug-${Math.random().toString(16).slice(2, 10)}`,
  reconnectPeriod: 5000,
});

client.on('connect', (connack) => console.log('connected', connack.reasonCode ?? 0));
client.on('error', (err) => console.error('error:', err.message, err.code));
client.on('disconnect', (packet) => console.warn('server disconnect, reason', packet.reasonCode));
client.on('close', () => console.warn('socket closed'));

Checklist

  1. Confirm the round trip on a known-good public broker in the online client.
  2. Match scheme, port and path to the broker's documented listener.
  3. Read the CONNACK code or reason code instead of guessing.
  4. Use a unique client ID and reconnect with backoff.
  5. Keep keep-alive shorter than any idle timeout on the path.

For a full test procedure, follow how to test an MQTT broker.

Frequently asked questions

What does MQTT connection refused not authorized mean?

The broker accepted the network connection but rejected the client. In MQTT 3.1.1 this is CONNACK return code 5, in MQTT 5 reason code 0x87. The credentials may be valid, but the account is not allowed to connect or use that client ID.

Why does my MQTT client keep disconnecting and reconnecting?

The most common cause is two clients using the same client ID. The broker disconnects the older session whenever a new one connects with that ID, and if both auto-reconnect they kick each other off in a loop. Give every client a unique ID.

Why does my MQTT connection time out instead of failing?

A timeout means no response at all, which usually points to a firewall silently dropping packets, a wrong host, or using the TCP port from a browser. A refusal, by contrast, means something answered and said no.