Cuando tu ESP32 no conecta a un broker MQTT, la causa casi siempre es uno de una docena de problemas previsibles, desde la banda Wi-Fi equivocada hasta un cliente TLS en un puerto sin cifrar. Esta guía te da un orden sistemático de comprobaciones, explica qué significa cada código de error de PubSubClient y muestra cómo confirmar cada solución contra broker.connectmq.com.
Empieza por el código de error
Antes de cambiar nada, averigua dónde falla la conexión. PubSubClient expone el motivo a través de client.state(). Imprímelo cada vez que connect() devuelva false:
if (!mqtt.connect(clientId.c_str())) {
Serial.printf("MQTT connect failed, state=%d\n", mqtt.state());
}
state() |
Constante | Qué suele significar |
|---|---|---|
| -4 | MQTT_CONNECTION_TIMEOUT |
El servidor no respondió dentro del periodo de keepalive |
| -3 | MQTT_CONNECTION_LOST |
La conexión se cayó después de establecerse |
| -2 | MQTT_CONNECT_FAILED |
No se pudo abrir en absoluto la conexión TCP o TLS |
| -1 | MQTT_DISCONNECTED |
El cliente está desconectado limpiamente |
| 1 | MQTT_CONNECT_BAD_PROTOCOL |
El broker no acepta la versión del protocolo |
| 2 | MQTT_CONNECT_BAD_CLIENT_ID |
Client ID rechazado |
| 3 | MQTT_CONNECT_UNAVAILABLE |
Broker no disponible |
| 4 | MQTT_CONNECT_BAD_CREDENTIALS |
Usuario o contraseña incorrectos |
| 5 | MQTT_CONNECT_UNAUTHORIZED |
Cliente no autorizado |
La división es útil: los códigos negativos indican que el problema está por debajo de MQTT (red, DNS, TCP, TLS), y los positivos, que el broker respondió y te rechazó. Un -2 te lleva a las soluciones 1–6; un 4 o un 5, a las soluciones 7–9.
Demuestra primero que el lado del broker funciona
Descarta el broker antes de tocar el firmware. Desde un equipo en la misma red, ejecuta:
mosquitto_sub -h broker.connectmq.com -p 1883 -i pc-test-4821 -t 'connectmq/demo/4821/#' -v
En un segundo terminal:
mosquitto_pub -h broker.connectmq.com -p 1883 -i pc-pub-4821 -t connectmq/demo/4821/hello -m test
Si el mensaje llega, el broker y el camino por internet funcionan, y el problema está en el ESP32 o en su segmento de red. Si tampoco llega desde tu equipo, revisa primero el firewall de tu red. La página del servidor MQTT público gratuito enumera los puertos y límites contra los que puedes probar.
Soluciones a nivel de red (códigos de estado negativos)
1. Asegúrate de que el Wi-Fi está realmente conectado en 2,4 GHz
El ESP32 (salvo las variantes más nuevas con soporte de 5 GHz) solo se comunica con redes de 2,4 GHz. Un router de doble banda con un mismo SSID para ambas bandas suele funcionar, pero un SSID solo de 5 GHz nunca funcionará. Comprueba también el orden de las operaciones: llamar a mqtt.connect() antes de que WiFi.status() == WL_CONNECTED falla siempre con -2. Imprime WiFi.localIP() y WiFi.RSSI(); una señal más débil que unos -80 dBm provoca fallos intermitentes que parecen problemas del broker.
2. Comprueba la resolución DNS
Si el hostname no se resuelve, la conexión TCP nunca empieza. Pruébalo directamente:
IPAddress ip;
if (WiFi.hostByName("broker.connectmq.com", ip)) {
Serial.println(ip);
} else {
Serial.println("DNS lookup failed");
}
Un fallo aquí apunta al servidor DNS que entrega el DHCP o a una configuración de IP estática sin entrada DNS. Cuando uses WiFi.config() para una dirección estática, pasa también la puerta de enlace y un servidor DNS.
3. Haz coincidir el puerto con la clase de cliente
Este es, con diferencia, el error más común. WiFiClient habla TCP sin cifrar y debe usar el puerto 1883. WiFiClientSecure habla TLS y debe usar el puerto 8883. Mezclarlos produce un -2 o un -4 sin más pistas.
| Clase de cliente | Puerto | Transporte |
|---|---|---|
WiFiClient |
1883 | MQTT sin cifrar sobre TCP |
WiFiClientSecure |
8883 | MQTT sobre TLS |
| Librerías WebSocket | 8083 / 8084 (ruta /mqtt) |
MQTT sobre WebSocket / WebSocket seguro |
PubSubClient no habla WebSocket, así que los puertos 8083 y 8084 son solo para clientes de navegador y librerías con soporte WebSocket.
4. Corrige la validación del certificado TLS
Con WiFiClientSecure, un certificado de CA ausente o incorrecto hace que el handshake falle en silencio. ConnectMQ usa un certificado de Let's Encrypt, así que carga la raíz ISRG Root X1 con setCACert(). Para confirmar que TLS es el culpable, llama temporalmente a secureClient.setInsecure(): si la conexión de repente funciona, tu CA es incorrecta. No lleves setInsecure() a producción. Lee el error TLS con secureClient.lastError(buf, sizeof(buf)) para obtener detalles, y consulta MQTT sobre SSL/TLS en ESP32 para una configuración segura completa.
5. Dale a TLS memoria y tiempo suficientes
Los handshakes TLS necesitan decenas de kilobytes de heap. Si tu sketch reserva búferes grandes, datos de imagen o varios clientes seguros, el handshake falla con -2. Imprime ESP.getFreeHeap() justo antes de conectar. Comprueba también el reloj: la validación del certificado compara fechas, y algunas librerías requieren una hora válida. Sincronizar NTP con configTime() antes de conectar evita toda una categoría de fallos desconcertantes.
6. Comprueba si la ruta de red bloquea puertos
Las Wi-Fi de invitados, las redes corporativas y algunos puntos de acceso móviles bloquean el 1883 o el 8883 de salida. Si la prueba con el equipo solo funcionó desde otra red, prueba el ESP32 con el punto de acceso de un teléfono. Si ahí conecta, la red original está filtrando los puertos MQTT y necesitas que el administrador de red los abra.
Soluciones a nivel de broker (códigos de estado positivos)
7. Usa un client ID no vacío y único
Cada cliente necesita su propio client ID. Dos dispositivos flasheados con el mismo ID fijo en el código se expulsarán mutuamente del broker una y otra vez, lo que parece desconexiones aleatorias con estado -3. Muchos brokers rechazan también un client ID vacío, incluida el área pública de broker.connectmq.com. Deriva el ID de la MAC del chip:
String clientId = "esp32-" + String((uint32_t)ESP.getEfuseMac(), HEX);
8. Usa exactamente las credenciales y el client ID del portal
En un broker privado de ConnectMQ, el client ID está vinculado a las credenciales emitidas. Si te inventas tu propio client ID mientras usas el usuario y la contraseña generados, el broker rechaza la conexión con el código 5 aunque la contraseña sea correcta. Copia los tres valores del portal: YOUR_CLIENT_ID, YOUR_USERNAME, YOUR_PASSWORD. Ojo con los espacios invisibles al final al pegarlos en el código.
9. Detén las tormentas de reintentos antes de que te bloqueen
Un bucle de reconexión sin espera puede lanzar al broker muchos intentos de conexión por segundo. Con credenciales incorrectas es especialmente dañino, porque ConnectMQ bloquea direcciones IP tras repetidos inicios de sesión fallidos como parte de su protección contra inundaciones. Espera entre intentos y aumenta la espera tras cada fallo:
unsigned long nextAttempt = 0;
unsigned long backoff = 2000;
void ensureMqtt() {
if (mqtt.connected() || millis() < nextAttempt) return;
if (mqtt.connect(clientId.c_str())) {
backoff = 2000;
mqtt.subscribe("connectmq/demo/4821/cmd");
} else {
Serial.printf("connect failed, state=%d, retry in %lu ms\n", mqtt.state(), backoff);
nextAttempt = millis() + backoff;
backoff = min(backoff * 2, 60000UL);
}
}
La estructura sin bloqueos también mantiene ágil loop(). La guía de reconexión MQTT en ESP32 profundiza en una lógica de reconexión robusta.
Soluciones para conexiones que se caen tras conectar
10. Llama a mqtt.loop() con la frecuencia suficiente
PubSubClient solo envía los pings de keepalive y lee los paquetes entrantes dentro de loop(). Un delay() largo o una lectura de sensor bloqueante que supere el intervalo de keepalive (15 segundos por defecto) hacen que el broker crea que el cliente ha muerto, y verás -3 o -4. Sustituye los delays largos por temporizadores con millis(), o sube el keepalive con mqtt.setKeepAlive(30). Si además la placa se reinicia, lee sobre reinicios del task watchdog del ESP32 en proyectos MQTT.
11. Aumenta el búfer para mensajes grandes
El búfer de paquetes por defecto de PubSubClient es de 256 bytes, incluidos el topic y la cabecera. Una publicación mayor falla sin hacer ruido: publish() devuelve false, y un mensaje entrante demasiado grande se descarta. Llama a mqtt.setBufferSize(1024) antes de conectar, dimensionado para tu mensaje más grande.
12. Respeta los límites del broker público
El área pública de broker.connectmq.com permite 64 KB por mensaje, 10 mensajes por segundo por cliente, 10 suscripciones por cliente y hasta 8 niveles de topic. Un sketch que publica en un bucle cerrado o se suscribe a muchos topics puede ser desconectado por superarlos. Limita la frecuencia de publicación, usa una suscripción con comodín en lugar de muchas individuales y mantén cortos los topics como connectmq/demo/4821/room/temp.
Un sketch de diagnóstico
Flashea esto cuando estés atascado. Informa de cada etapa por separado, así que verás exactamente dónde se rompe la cadena:
#include <WiFi.h>
#include <PubSubClient.h>
const char* WIFI_SSID = "YOUR_WIFI";
const char* WIFI_PASS = "YOUR_WIFI_PASSWORD";
const char* HOST = "broker.connectmq.com";
WiFiClient net;
PubSubClient mqtt(net);
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) { delay(250); Serial.print('.'); }
Serial.printf("\nWi-Fi OK ip=%s rssi=%d\n", WiFi.localIP().toString().c_str(), WiFi.RSSI());
IPAddress ip;
if (!WiFi.hostByName(HOST, ip)) { Serial.println("DNS FAILED"); return; }
Serial.printf("DNS OK %s\n", ip.toString().c_str());
if (!net.connect(HOST, 1883)) { Serial.println("TCP 1883 FAILED"); return; }
Serial.println("TCP OK");
net.stop();
String id = "esp32-diag-" + String((uint32_t)ESP.getEfuseMac(), HEX);
mqtt.setServer(HOST, 1883);
if (mqtt.connect(id.c_str())) {
Serial.println("MQTT OK");
mqtt.publish("connectmq/demo/4821/diag", "hello from diag");
} else {
Serial.printf("MQTT FAILED state=%d\n", mqtt.state());
}
}
void loop() { mqtt.loop(); }
Mantén mosquitto_sub en marcha sobre connectmq/demo/4821/# y verás el mensaje de diagnóstico en cuanto todo funcione.
Cuando funciona en el broker de pruebas pero no en producción
La mayoría de los fallos en producción se reducen a las soluciones 3, 4 y 8: pasar al puerto 8883 sin pasar a WiFiClientSecure, olvidar el certificado de CA o usar un client ID inventado con credenciales del portal. Cambia una sola cosa cada vez y sigue imprimiendo el código de error. El tutorial de IoT con ESP32 muestra un proyecto completo que conecta sin problemas en ambos modos, y qué hace un broker MQTT explica por qué el broker rechaza ciertas conexiones.
Pon tu ESP32 en línea con ConnectMQ
En cuanto el dispositivo conecte con el broker de pruebas, pásalo a un broker privado en el plan gratuito Serverless en app.connectmq.com, sin tarjeta de crédito. El cliente MQTT del navegador y el explorador de topics en vivo del portal facilitan confirmar el tráfico tanto desde el lado del dispositivo como desde el del servidor. ConnectMQ es un broker MQTT gestionado con TLS en el 8883 de serie, así que el único certificado que necesitas en el ESP32 es la raíz pública de Let's Encrypt.
Preguntas frecuentes
¿Qué significa rc=-2 en PubSubClient?
Significa que no se pudo abrir la conexión TCP o TLS, así que el broker nunca vio un paquete MQTT. Revisa el Wi-Fi, el DNS, la combinación de puerto y clase de cliente, el certificado de CA y el heap libre.
¿Por qué mi ESP32 se conecta y se desconecta cada pocos segundos?
Normalmente otro dispositivo está usando el mismo client ID, o mqtt.loop() no se llama con la frecuencia suficiente para enviar los pings de keepalive. Da a cada dispositivo un ID único y elimina los delays largos que bloquean.
¿Puedo usar el puerto 8083 u 8084 con PubSubClient?
No. Son puertos WebSocket, y PubSubClient solo habla MQTT sobre TCP directo o TLS. Usa el 1883 con WiFiClient o el 8883 con WiFiClientSecure.
¿Por qué se rechazan mis credenciales aunque la contraseña sea correcta?
En un broker privado con credenciales emitidas, el client ID está vinculado a ellas. Usa exactamente el client ID que muestra el portal junto con el usuario y la contraseña generados.



