ESP32 and Arduino

PubSubClient rc=-2 and Other State Codes Explained (With Fixes)

PubSubClient prints "failed, rc=-2" and nothing else? This guide decodes every state code from -4 to 5 and gives tested fixes for DNS, port, TLS, client ID and credential problems.

Serial monitor showing a PubSubClient rc=-2 connection error next to an ESP32 board
Run this on your own MQTT brokerGet a private, managed MQTT broker on the Free plan and start from ready-made code samples.
Try it free Code samples No card required

Seeing failed, rc=-2 in the serial monitor is the most common PubSubClient error, and the message alone tells you very little. PubSubClient rc=-2 means the TCP (or TLS) connection to the broker never opened, so the problem is almost always DNS, the port, the network or the certificate, not MQTT itself. This guide decodes every state code from -4 to 5 and gives you a fix for each.

Where the rc Value Comes From

PubSubClient stores the result of the last connection attempt in an internal state variable that you read with mqtt.state(). Most example sketches print it when connect() returns false:

if (!mqtt.connect(clientId)) {
  Serial.print("failed, rc=");
  Serial.println(mqtt.state());
}

The values are defined as constants in PubSubClient.h. Negative values come from the library and the network layer. Positive values are the return codes the broker sent back in its CONNACK packet, as defined by the MQTT 3.1.1 specification.

rc Constant Layer Plain-language meaning
-4 MQTT_CONNECTION_TIMEOUT Library Socket opened, but no CONNACK (or no ping response) in time
-3 MQTT_CONNECTION_LOST Network An established connection broke
-2 MQTT_CONNECT_FAILED Network Could not open the socket at all
-1 MQTT_DISCONNECTED Library Client is disconnected cleanly
0 MQTT_CONNECTED Broker Connected
1 MQTT_CONNECT_BAD_PROTOCOL Broker Protocol version not accepted
2 MQTT_CONNECT_BAD_CLIENT_ID Broker Client ID rejected
3 MQTT_CONNECT_UNAVAILABLE Broker Broker cannot accept connections now
4 MQTT_CONNECT_BAD_CREDENTIALS Broker Username or password wrong
5 MQTT_CONNECT_UNAUTHORIZED Broker Not authorised to connect

The key insight: with negative codes the broker either never saw you or never answered. With positive codes the broker received your CONNECT packet and deliberately refused it.

Fixing PubSubClient rc=-2

Because rc=-2 is reported whenever client.connect(host, port) on the underlying network object fails, it has many possible causes. Work through them in this order.

1. Wi-Fi Is Not Actually Connected

Calling mqtt.connect() before WiFi.status() == WL_CONNECTED always returns -2. It also happens after the access point drops you and your loop only checks MQTT. Check Wi-Fi first in every iteration and reconnect it before touching MQTT.

2. DNS Fails or the Host Name Is Wrong

Typos such as broker.connectmq.com with a trailing space, mqtt://broker.connectmq.com (a URL instead of a host name) or http:// prefixes all fail silently. setServer() expects a bare host name or an IPAddress. Test DNS directly:

IPAddress ip;
if (WiFi.hostByName("broker.connectmq.com", ip)) {
  Serial.print("Resolved to ");
  Serial.println(ip);
} else {
  Serial.println("DNS lookup failed");
}

If DNS fails, check that DHCP gave you a DNS server, or set one explicitly with WiFi.config().

3. Wrong Port for the Client Type

This is the classic mistake. Port and network client must match:

Port Transport Arduino client to use
1883 Plain MQTT over TCP WiFiClient
8883 MQTT over TLS WiFiClientSecure
8083 / 8084 MQTT over WebSocket Not supported by PubSubClient

A WiFiClient pointed at 8883 opens a TCP socket and then hangs or fails because the broker expects a TLS handshake, which often shows up as -2 or -4. A WiFiClientSecure pointed at 1883 fails the handshake and returns -2. PubSubClient cannot speak WebSockets at all, so ports 8083 and 8084 are for browsers and tools such as those described in MQTT over WebSocket.

4. TLS Certificate Problems

With WiFiClientSecure, a failed certificate check is reported as rc=-2 too. Common causes:

  • No CA certificate set. On ESP32, call setCACert() with the root that signed the broker certificate. ConnectMQ uses Let's Encrypt, so the root is ISRG Root X1.
  • Pinning a leaf or intermediate certificate that has since rotated. Let's Encrypt certificates are short-lived; pin the root, not the leaf.
  • Wrong system time on ESP8266. BearSSL checks validity dates, so sync time with configTime() before connecting.
  • Out of memory. The TLS handshake needs a sizeable chunk of heap; on ESP8266 a large MQTT buffer plus TLS can exhaust it.

On ESP32 you can print the underlying TLS error after a failed attempt:

char err[128];
int code = netClient.lastError(err, sizeof(err));
Serial.printf("TLS error %d: %s\n", code, err);

To isolate the problem, temporarily call netClient.setInsecure(). If that connects, your certificate configuration is the issue. Never ship firmware with setInsecure().

5. The Network Blocks Outbound MQTT

Corporate, school and some guest networks block outbound ports 1883 and 8883. Try the same sketch on a phone hotspot. If it works there, ask for the port to be opened or move the device to a different network segment.

6. Connecting Too Often

If the broker sees repeated failed attempts from your IP address, it may temporarily refuse connections. ConnectMQ, for example, blocks IP addresses after repeated failed logins to protect tenants. A reconnect loop without a delay can turn a single wrong password into a lockout that then looks like rc=-2. Always wait at least a few seconds between attempts and back off further after several failures.

Fixing rc=-4 (Connection Timeout)

Here the socket opened, but the CONNACK did not arrive within the socket timeout (15 seconds by default). Typical causes:

  • Plain WiFiClient on a TLS port, as described above.
  • Weak Wi-Fi signal with heavy packet loss. Print WiFi.RSSI(); anything around -80 dBm or weaker is unreliable.
  • Blocking code. If the connection was established earlier, -4 can also appear later when loop() is not called often enough for keep alive pings to be answered.

Fixing rc=-3 (Connection Lost)

The connection worked and then broke. Look for:

  • Duplicate client IDs. When a second device connects with the same ID, the broker disconnects the first. Two boards flashed with the same sketch will take turns kicking each other off. Generate IDs from the MAC address.
  • Keep alive expiry. Long delay() calls or slow sensor reads starve mqtt.loop().
  • Oversized or too frequent messages. Brokers enforce limits. On the public MQTT test broker at broker.connectmq.com, clients are limited to 64 KB per message and 10 messages per second, and exceeding limits can close the session.

Fixing the Positive Codes (1 to 5)

rc=1: Bad Protocol

PubSubClient sends MQTT 3.1.1 by default. You only see this if you compiled with MQTT_VERSION set to 3.1 against a broker that rejects it. Remove the override.

rc=2: Client ID Rejected

An empty client ID is the usual culprit. The ConnectMQ public broker requires a non-empty client ID even for anonymous connections. On a private ConnectMQ broker, client IDs are bound to the credentials issued in the portal, so use exactly the client ID you were given.

rc=3: Server Unavailable

The broker is up but not accepting connections, for example during maintenance or when a connection quota has been reached. Back off and retry. If it persists on your own broker, check whether you have exceeded your plan's connection limit.

rc=4: Bad Username or Password

Copy the credentials again from the portal and watch for invisible characters. Remember that connect(id, user, pass) takes the client ID first; swapping the order is surprisingly common.

rc=5: Not Authorised

The broker understood the credentials but refuses this client, for instance because the username and client ID do not belong together. Recheck all three values.

A Diagnostic Connect Function

Drop this into any sketch to get a readable reason instead of a bare number:

const char* mqttStateText(int s) {
  switch (s) {
    case -4: return "timeout (no CONNACK) - check port/TLS match and signal";
    case -3: return "connection lost - duplicate client ID or keep alive";
    case -2: return "socket failed - Wi-Fi, DNS, port, firewall or TLS cert";
    case -1: return "disconnected";
    case 0:  return "connected";
    case 1:  return "bad protocol version";
    case 2:  return "client ID rejected - empty or not allowed";
    case 3:  return "server unavailable - retry later";
    case 4:  return "bad username or password";
    case 5:  return "not authorised";
    default: return "unknown";
  }
}

bool connectWithDiagnostics() {
  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("Wi-Fi not connected, skipping MQTT");
    return false;
  }
  if (mqtt.connect(DEVICE_ID)) {
    Serial.println("MQTT connected");
    return true;
  }
  int s = mqtt.state();
  Serial.printf("MQTT failed rc=%d: %s\n", s, mqttStateText(s));
  return false;
}

Quick Isolation Checklist

  1. Can your computer reach the broker? Run mosquitto_sub -h broker.connectmq.com -p 1883 -i test-$RANDOM -t "connectmq/demo/check/#" -v. If this fails, the issue is your network, not the board.
  2. Does the board resolve the host name? Use the DNS snippet above.
  3. Does it connect on 1883 with WiFiClient and an anonymous, unique client ID? If yes, your TLS or credentials are the issue.
  4. Does it connect on 8883 with setInsecure()? If yes, fix the CA certificate.
  5. Does it connect with the CA but fail with credentials? Recheck user, password and client ID.

Once connected, the next problem people hit is message size; see PubSubClient buffer size. For a full walkthrough of the library, read our PubSubClient Arduino tutorial, and for a complete project see the ESP32 IoT tutorial pillar guide.

Stop Chasing rc Codes with a Managed Broker

Many rc=-2 and rc=-4 errors come from self-hosted brokers with expired certificates, closed firewall ports or crashed services. A fully managed MQTT service such as ConnectMQ takes those variables away: TLS on 8883 with an automatically renewed Let's Encrypt certificate, credentials generated for you, and a browser MQTT client in the portal so you can confirm the broker side works before you debug the device. Start a free Serverless broker at app.connectmq.com, no credit card needed.

Frequently Asked Questions

What does rc=-2 mean in PubSubClient?

It means the network connection to the broker could not be opened. The broker never received your CONNECT packet, so check Wi-Fi, DNS, the port and, for TLS, the CA certificate.

Why do I get rc=-2 only on port 8883?

Your TLS setup is failing. Make sure you use WiFiClientSecure, load the correct root certificate (ISRG Root X1 for Let's Encrypt brokers) and, on ESP8266, set the clock before connecting.

My board connects and then shows rc=-3 every few seconds. Why?

Most often another device or tool is using the same client ID, so the broker keeps replacing one session with the other. Give each device a unique ID, for example based on its MAC address.

Is rc=5 different from rc=4?

Yes. rc=4 means the username or password is wrong. rc=5 means the credentials were understood but this client is not allowed to connect, often because the client ID does not match the credentials.

Related articles

ESP-IDF MQTT Example: Build an esp-mqtt Client from Scratch

Build an ESP-IDF project from an empty folder with the esp-mqtt component: Wi-Fi station setup, the MQTT event handler, publish and subscribe, MQTT 5 properties and TLS with the certificate bundle.

8 min read

Using esp-mqtt MQTT 5 Features on ESP32 with ESP-IDF

A hands-on ESP-IDF project that enables MQTT 5.0 in esp-mqtt and uses user properties, message expiry, topic aliases, session expiry and response topics against a real MQTT 5 broker.

7 min read

All articles →

Your private MQTT broker is a few clicks away

Create an account, get your broker credentials and connect your first device — no servers to install, patch or scale.