When an ESP32 refuses to talk to an MQTT broker, the serial monitor usually shows one unhelpful line: failed, rc=-2 try again in 5 seconds. The fastest way out is to test the connection layer by layer — Wi-Fi, DNS, TCP port, MQTT handshake, then publishing — and stop at the first layer that fails.
This guide gives you a diagnostic sketch for the Arduino core (ESP32 core 3.x with PubSubClient 2.8) that does exactly that, explains every result, and shows how to confirm from the broker side that the board is really there.
The checklist at a glance
| Step | What can go wrong | How it shows up |
|---|---|---|
| 1. Serial log | Wrong baud rate, no log at all | Garbage or silence in the monitor |
| 2. Wi-Fi | Wrong SSID/password, 5 GHz network, weak signal | WiFi.status() never reaches WL_CONNECTED (3) |
| 3. DNS | No DNS server from DHCP, typo in host | hostByName fails |
| 4. TCP port | Firewall, wrong port, TLS port with a plain client | rc=-2 from PubSubClient |
| 5. MQTT CONNECT | Duplicate client ID, bad credentials, ACL | rc=2, rc=4, rc=5 |
| 6. Publish | Packet bigger than the buffer | publish() returns false |
| 7. Staying connected | loop() not called, keep alive too short | rc=-3 after a while |
Step 1: get a readable serial log
Set the monitor to the same speed as Serial.begin() — 115200 is the convention. In the Arduino IDE, also set Tools → Core Debug Level to Info or Verbose: the ESP32 core then prints Wi-Fi events and TLS errors that your own sketch never sees. In PlatformIO, use monitor_speed = 115200 and build_flags = -DCORE_DEBUG_LEVEL=4.
Step 2–6: the diagnostic sketch
Flash this sketch before touching your real firmware. It checks every layer in order and prints exactly where it stops.
#include <WiFi.h>
#include <PubSubClient.h>
const char* WIFI_SSID = "your-ssid";
const char* WIFI_PASS = "your-wifi-password";
const char* MQTT_HOST = "broker.connectmq.com";
const uint16_t MQTT_PORT = 1883;
const char* MQTT_USER = "username"; // nullptr if the broker allows anonymous access
const char* MQTT_PASS = "password";
WiFiClient net;
PubSubClient mqtt(net);
String clientId;
bool check() {
Serial.println("\n[1] Wi-Fi");
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASS);
unsigned long t0 = millis();
while (WiFi.status() != WL_CONNECTED && millis() - t0 < 20000) { delay(250); Serial.print('.'); }
if (WiFi.status() != WL_CONNECTED) {
Serial.printf("\nWi-Fi failed, status=%d\n", WiFi.status());
return false;
}
Serial.printf("\nIP %s gateway %s RSSI %d dBm\n", WiFi.localIP().toString().c_str(),
WiFi.gatewayIP().toString().c_str(), WiFi.RSSI());
Serial.println("[2] DNS");
IPAddress ip;
if (!WiFi.hostByName(MQTT_HOST, ip)) { Serial.println("DNS lookup failed"); return false; }
Serial.printf("%s -> %s\n", MQTT_HOST, ip.toString().c_str());
Serial.println("[3] TCP port");
if (!net.connect(ip, MQTT_PORT)) { Serial.println("TCP connect failed"); return false; }
Serial.println("TCP OK");
net.stop();
Serial.println("[4] MQTT CONNECT");
clientId = "esp32-" + WiFi.macAddress();
clientId.replace(":", "");
mqtt.setServer(MQTT_HOST, MQTT_PORT);
mqtt.setBufferSize(1024);
mqtt.setKeepAlive(30);
if (!mqtt.connect(clientId.c_str(), MQTT_USER, MQTT_PASS)) {
Serial.printf("MQTT failed, rc=%d\n", mqtt.state());
return false;
}
Serial.printf("MQTT OK as %s\n", clientId.c_str());
Serial.println("[5] PUBLISH");
String topic = "testmqtt/esp32-check/" + clientId + "/hello";
bool ok = mqtt.publish(topic.c_str(), "ESP32 connection test passed");
Serial.printf("publish to %s: %s\n", topic.c_str(), ok ? "OK" : "FAILED");
return ok;
}
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println(check() ? "\nALL CHECKS PASSED" : "\nCHECK FAILED - see the last step above");
}
void loop() {
mqtt.loop();
}Reading the results
Wi-Fi status codes
status | Meaning | Fix |
|---|---|---|
1 WL_NO_SSID_AVAIL | Network not found | Check the SSID spelling; make sure it broadcasts on 2.4 GHz |
4 WL_CONNECT_FAILED | Rejected, usually wrong password | Re-type the password; WPA3-only networks may need WPA2/WPA3 mixed mode |
6 WL_DISCONNECTED | Still trying or dropped | Move closer; RSSI below about -80 dBm is unreliable |
A good RSSI is roughly -50 to -70 dBm. If Wi-Fi connects but keeps dropping later, see ESP32 Wi-Fi and MQTT disconnects.
DNS and TCP
DNS lookup failed means the board got no usable DNS server from DHCP or the host name is wrong. Check the same name from a laptop on the same network with nslookup broker.connectmq.com. As a temporary test you can connect by IP, but do not ship that.
TCP connect failed (and rc=-2 in your own firmware) means nothing answered on that host and port. Common causes: a guest network or corporate firewall blocking 1883, a typo in the port, or using port 8883 with a plain WiFiClient — TLS ports need WiFiClientSecure and a CA certificate, as shown in ESP32 MQTT over TLS. Confirm from a laptop on the same Wi-Fi with nc -vz broker.connectmq.com 1883.
PubSubClient return codes
rc | Constant | Usual cause |
|---|---|---|
| -4 | MQTT_CONNECTION_TIMEOUT | No CONNACK in time, or keep alive expired |
| -3 | MQTT_CONNECTION_LOST | Connection dropped; loop() not called often enough |
| -2 | MQTT_CONNECT_FAILED | TCP connect failed (see above) |
| -1 | MQTT_DISCONNECTED | Clean disconnect / never connected |
| 1 | MQTT_CONNECT_BAD_PROTOCOL | Broker does not accept MQTT 3.1.1 |
| 2 | MQTT_CONNECT_BAD_CLIENT_ID | Client ID rejected (empty, too long, invalid characters) |
| 3 | MQTT_CONNECT_UNAVAILABLE | Broker unavailable |
| 4 | MQTT_CONNECT_BAD_CREDENTIALS | Wrong username or password |
| 5 | MQTT_CONNECT_UNAUTHORIZED | Not authorized — credentials missing or client not allowed |
Positive values are the broker's own CONNACK answer, so the network is fine and only configuration remains. The reason code reference lists the matching MQTT codes, and PubSubClient not connecting goes deeper into each one.
Publish returns false
PubSubClient's default buffer is 256 bytes and must hold the whole packet: header, topic and payload. A JSON payload of 200 bytes on a long topic already exceeds it, and publish() simply returns false with no message on the broker. Call setBufferSize() before connecting; the packet size calculator tells you how big a packet really is.
Step 7: staying connected
mqtt.loop() must run often — at least several times per keep-alive interval. A delay(60000) in loop() or a blocking sensor read guarantees rc=-3 after about one and a half keep-alive periods, because the broker never hears a PINGREQ. Replace long delays with millis() timers, and reconnect with a backoff instead of blocking in a while (!mqtt.connected()) loop.
Confirm from the broker side
A serial line saying "OK" is the device's opinion. Verify it independently: open the online MQTT client, connect to the same broker and subscribe to testmqtt/esp32-check/#. Reset the board and you should see the hello message arrive within a few seconds. From a terminal the same check is:
mosquitto_sub -h broker.connectmq.com -p 1883 \
-u "$MQTT_USER" -P "$MQTT_PASS" \
-t 'testmqtt/esp32-check/#' -vWatch for your ESP32 in the browser
Most ESP32 MQTT problems are not MQTT problems at all — they are Wi-Fi, DNS or port issues hidden behind a single return code. Test one layer at a time, read the exact status or rc value, and confirm on the broker side before you move on to your real firmware. For the full Arduino setup, continue with ESP32 MQTT with Arduino.
Frequently asked questions
What does "failed, rc=-2" mean on an ESP32?
It is PubSubClient's MQTT_CONNECT_FAILED: the TCP connection to the broker could not be opened. The cause is below MQTT — no Wi-Fi, wrong host or port, DNS failure, a firewall, or a TLS port used with a plain WiFiClient.
Why does my ESP32 connect but publish() returns false?
Most often the message is larger than PubSubClient's buffer, which is 256 bytes by default including the topic and header. Call setBufferSize(1024) or larger before connecting, and check the return value of every publish.
Does the ESP32 support 5 GHz Wi-Fi for MQTT?
The classic ESP32, S2, S3 and C3 only have 2.4 GHz radios, so they cannot join a 5 GHz-only network. Newer chips such as the ESP32-C5 support dual band. Check your exact module before debugging MQTT.

