Cuando un ESP32 se niega a hablar con un broker MQTT, el monitor serie suele mostrar una única línea poco útil: failed, rc=-2 try again in 5 seconds. La forma más rápida de salir es probar la conexión capa por capa (Wi-Fi, DNS, puerto TCP, handshake MQTT y publicación) y detenerse en la primera capa que falle.
Esta guía te da un sketch de diagnóstico para el core de Arduino (core ESP32 3.x con PubSubClient 2.8) que hace exactamente eso, explica cada resultado y muestra cómo confirmar desde el lado del broker que la placa está realmente ahí.
El checklist de un vistazo
| Paso | Qué puede fallar | Cómo se manifiesta |
|---|---|---|
| 1. Log serie | Baudios incorrectos, ningún log | Basura o silencio en el monitor |
| 2. Wi-Fi | SSID/contraseña incorrectos, red de 5 GHz, señal débil | WiFi.status() nunca llega a WL_CONNECTED (3) |
| 3. DNS | DHCP sin servidor DNS, errata en el host | hostByName falla |
| 4. Puerto TCP | Firewall, puerto incorrecto, puerto TLS con cliente sin TLS | rc=-2 de PubSubClient |
| 5. MQTT CONNECT | Client ID duplicado, credenciales erróneas, ACL | rc=2, rc=4, rc=5 |
| 6. Publicación | Paquete más grande que el buffer | publish() devuelve false |
| 7. Mantener la conexión | No se llama a loop(), keep alive demasiado corto | rc=-3 al cabo de un rato |
Paso 1: consigue un log serie legible
Configura el monitor a la misma velocidad que Serial.begin(); 115200 es lo habitual. En el Arduino IDE, pon también Tools → Core Debug Level en Info o Verbose: así el core del ESP32 imprime los eventos de Wi-Fi y los errores TLS que tu sketch nunca ve. En PlatformIO, usa monitor_speed = 115200 y build_flags = -DCORE_DEBUG_LEVEL=4.
Pasos 2–6: el sketch de diagnóstico
Flashea este sketch antes de tocar tu firmware real. Comprueba cada capa en orden e imprime exactamente dónde se detiene.
#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 si el broker permite acceso anónimo
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();
}Interpretar los resultados
Códigos de estado del Wi-Fi
status | Significado | Solución |
|---|---|---|
1 WL_NO_SSID_AVAIL | Red no encontrada | Revisa cómo está escrito el SSID; asegúrate de que emite en 2,4 GHz |
4 WL_CONNECT_FAILED | Rechazado, normalmente contraseña incorrecta | Vuelve a escribir la contraseña; las redes solo WPA3 pueden necesitar el modo mixto WPA2/WPA3 |
6 WL_DISCONNECTED | Sigue intentándolo o se cayó | Acércate; un RSSI por debajo de unos -80 dBm no es fiable |
Un buen RSSI está más o menos entre -50 y -70 dBm. Si el Wi-Fi conecta pero se cae después, consulta desconexiones de Wi-Fi y MQTT en el ESP32.
DNS y TCP
DNS lookup failed significa que la placa no recibió un servidor DNS utilizable por DHCP o que el nombre del host está mal. Comprueba el mismo nombre desde un portátil en la misma red con nslookup broker.connectmq.com. Como prueba temporal puedes conectar por IP, pero no lo dejes así en producción.
TCP connect failed (y rc=-2 en tu propio firmware) significa que nada respondió en ese host y puerto. Causas comunes: una red de invitados o un firewall corporativo que bloquea el 1883, una errata en el puerto o usar el puerto 8883 con un WiFiClient normal; los puertos TLS necesitan WiFiClientSecure y un certificado de CA, como se muestra en MQTT sobre TLS con ESP32. Confírmalo desde un portátil en el mismo Wi-Fi con nc -vz broker.connectmq.com 1883.
Códigos de retorno de PubSubClient
rc | Constante | Causa habitual |
|---|---|---|
| -4 | MQTT_CONNECTION_TIMEOUT | No llegó el CONNACK a tiempo o expiró el keep alive |
| -3 | MQTT_CONNECTION_LOST | Se perdió la conexión; no se llama a loop() con suficiente frecuencia |
| -2 | MQTT_CONNECT_FAILED | Falló la conexión TCP (ver arriba) |
| -1 | MQTT_DISCONNECTED | Desconexión limpia / nunca conectado |
| 1 | MQTT_CONNECT_BAD_PROTOCOL | El broker no acepta MQTT 3.1.1 |
| 2 | MQTT_CONNECT_BAD_CLIENT_ID | Client ID rechazado (vacío, demasiado largo, caracteres no válidos) |
| 3 | MQTT_CONNECT_UNAVAILABLE | Broker no disponible |
| 4 | MQTT_CONNECT_BAD_CREDENTIALS | Usuario o contraseña incorrectos |
| 5 | MQTT_CONNECT_UNAUTHORIZED | No autorizado: faltan credenciales o el cliente no tiene permiso |
Los valores positivos son la respuesta CONNACK del propio broker, así que la red está bien y solo queda la configuración. La referencia de reason codes muestra los códigos MQTT equivalentes, y PubSubClient no conecta profundiza en cada uno.
publish() devuelve false
El buffer por defecto de PubSubClient es de 256 bytes y debe contener el paquete completo: cabecera, topic y payload. Un payload JSON de 200 bytes en un topic largo ya lo supera, y publish() simplemente devuelve false sin que llegue nada al broker. Llama a setBufferSize() antes de conectar; la calculadora de tamaño de paquete te dice lo grande que es realmente un paquete.
Paso 7: mantener la conexión
mqtt.loop() debe ejecutarse a menudo, al menos varias veces por intervalo de keep alive. Un delay(60000) en loop() o una lectura de sensor bloqueante garantizan un rc=-3 tras aproximadamente un periodo y medio de keep alive, porque el broker nunca recibe un PINGREQ. Sustituye los delays largos por temporizadores con millis() y reconecta con backoff en lugar de bloquear en un bucle while (!mqtt.connected()).
Confírmalo desde el lado del broker
Una línea serie que dice "OK" es la opinión del dispositivo. Verifícalo de forma independiente: abre el cliente MQTT online, conéctate al mismo broker y suscríbete a testmqtt/esp32-check/#. Reinicia la placa y deberías ver llegar el mensaje hello en pocos segundos. Desde una terminal, la misma comprobación es:
mosquitto_sub -h broker.connectmq.com -p 1883 \
-u "$MQTT_USER" -P "$MQTT_PASS" \
-t 'testmqtt/esp32-check/#' -vEspera a tu ESP32 en el navegador
La mayoría de los problemas MQTT del ESP32 no son problemas de MQTT: son problemas de Wi-Fi, DNS o puertos escondidos tras un único código de retorno. Prueba una capa cada vez, lee el valor exacto de status o rc y confírmalo en el lado del broker antes de pasar a tu firmware real. Para la configuración completa con Arduino, continúa con MQTT en ESP32 con Arduino.
Preguntas frecuentes
¿Qué significa "failed, rc=-2" en un ESP32?
Es MQTT_CONNECT_FAILED de PubSubClient: no se pudo abrir la conexión TCP con el broker. La causa está por debajo de MQTT: sin Wi-Fi, host o puerto incorrectos, fallo de DNS, un firewall o un puerto TLS usado con un WiFiClient normal.
¿Por qué mi ESP32 conecta pero publish() devuelve false?
Casi siempre el mensaje es más grande que el buffer de PubSubClient, que por defecto es de 256 bytes incluyendo topic y cabecera. Llama a setBufferSize(1024) o más antes de conectar y comprueba el valor de retorno de cada publish.
¿El ESP32 admite Wi-Fi de 5 GHz para MQTT?
El ESP32 clásico, el S2, el S3 y el C3 solo tienen radio de 2,4 GHz, así que no pueden unirse a una red solo de 5 GHz. Chips más nuevos como el ESP32-C5 son de doble banda. Comprueba tu módulo exacto antes de depurar MQTT.

