MQTT normalmente funciona sobre una conexión TCP directa en el puerto 1883 (o 8883 con TLS). Los navegadores no pueden abrir sockets TCP directos, así que una aplicación web no puede hablar MQTT de esa forma. La solución es MQTT sobre WebSockets: los mismos paquetes MQTT, transportados dentro de una conexión WebSocket que todos los navegadores soportan. Esta guía explica cómo funciona y cómo evitar los errores con los que tropieza casi todo el mundo.
Por qué los navegadores necesitan WebSockets
Por motivos de seguridad, el JavaScript de una página web solo puede establecer conexiones de red a través de las APIs del navegador: fetch, XMLHttpRequest, WebSocket, WebRTC y WebTransport. Ninguna puede abrir una conexión TCP arbitraria al puerto 1883. Los WebSockets encajan de forma natural con MQTT porque ofrecen un canal binario, bidireccional y de larga duración: justo lo que MQTT necesita.
Usar WebSockets no es solo cosa de navegadores. También ayuda cuando:
- un firewall o proxy corporativo solo permite tráfico web en los puertos 80 y 443;
- quieres que MQTT comparta balanceador de carga o reverse proxy con tus servicios HTTP;
- estás construyendo un dashboard, una herramienta de administración o una app web móvil que muestra datos de dispositivos en directo.
Cómo funciona MQTT sobre WebSockets
- El cliente abre una conexión HTTP(S) y envía una petición de upgrade a WebSocket a una URL como
wss://broker.emqx.io:8084/mqtt, solicitando el subprotocolomqtt. - El broker responde
101 Switching Protocolsy la conexión pasa a ser un WebSocket. - A partir de ahí, los paquetes MQTT (
CONNECT,PUBLISH, …) viajan dentro de frames binarios de WebSocket. Todo lo que está por encima del transporte —QoS, mensajes retenidos, Last Will, propiedades de MQTT 5— funciona exactamente igual que sobre TCP.
El broker debe tener habilitado un listener WebSocket. Ese listener suele ejecutarse en un puerto distinto del listener TCP, y algunos brokers esperan una ruta (path) concreta.
Una consecuencia práctica: los navegadores no dejan que JavaScript añada cabeceras personalizadas a una petición WebSocket, así que la autenticación normalmente se hace dentro del propio MQTT, con los campos de usuario y contraseña del paquete CONNECT, en lugar de con cabeceras HTTP o cookies.
ws vs wss
ws:// | wss:// | |
|---|---|---|
| Cifrado | Ninguno: credenciales y payloads en texto plano | TLS, como HTTPS |
| Utilizable desde páginas HTTPS | No (se bloquea como contenido mixto) | Sí |
| Proxies y firewalls | A veces interfieren | Normalmente pasa por el puerto 443 |
| Uso típico | Solo desarrollo local | Todo lo demás |
Como wss:// usa la pila TLS del navegador, el certificado del broker debe ser de confianza para el navegador. Un certificado autofirmado fallará en silencio desde JavaScript: abre una vez la URL https://host:port en una pestaña para ver el error de certificado real.
Contenido mixto: el error más habitual en el navegador
Si tu página se sirve por https:// (como casi todos los sitios), el navegador bloquea cualquier intento de abrir una conexión ws://. La consola muestra un error de contenido mixto o de seguridad y el cliente MQTT simplemente nunca se conecta. La solución es siempre la misma: usar el endpoint wss:// del broker. Nuestro cliente MQTT online se sirve por HTTPS por este motivo y solo se conecta mediante wss://.
Puertos y rutas
No existe un puerto WebSocket estándar único para MQTT; cada broker elige el suyo. Estos son los endpoints de los brokers públicos de nuestro directorio de brokers:
| Broker | ws:// | wss:// | Ruta |
|---|---|---|---|
| broker.hivemq.com | 8000 | 8884 | /mqtt |
| broker.emqx.io | 8083 | 8084 | /mqtt |
| test.mosquitto.org | 8080 | 8081 | cualquiera |
| mqtt.eclipseprojects.io | 80 | 443 | /mqtt |
Una ruta incorrecta suele producir un handshake fallido (HTTP 404 o una conexión cerrada) antes incluso de que empiece MQTT. Un puerto incorrecto suele acabar en timeout. Para una visión completa de los puertos TCP, TLS y WebSocket, consulta puertos MQTT explicados.
Ejemplo: MQTT en el navegador con MQTT.js
MQTT.js es el cliente JavaScript más utilizado y funciona tanto en Node.js como en el navegador. En el navegador usa WebSockets automáticamente:
import mqtt from 'mqtt';
const client = mqtt.connect('wss://broker.emqx.io:8084/mqtt', {
clientId: `web-${crypto.randomUUID().slice(0, 8)}`,
// username: 'user', password: 'secret', // for private brokers
reconnectPeriod: 2000,
});
client.on('connect', () => {
client.subscribe('testmqtt/ws-demo/#');
client.publish('testmqtt/ws-demo/hello', 'hello from the browser');
});
client.on('message', (topic, payload) => {
console.log(topic, payload.toString());
});
client.on('error', (err) => console.error('MQTT error:', err.message));Habilitar WebSockets en tu propio broker
Si gestionas tu propio broker, el listener WebSocket suele ser cuestión de unas pocas líneas de configuración. En Mosquitto, por ejemplo, añades un segundo listener junto al de TCP:
listener 1883
protocol mqtt
listener 8080
protocol websocketsPara wss:// puedes configurar los certificados directamente en ese listener o, lo más habitual, terminar TLS en un reverse proxy en el puerto 443 y reenviar el tráfico WebSocket sin cifrar al broker. El proxy necesita tener habilitado el soporte de WebSocket:
location /mqtt {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 1h;
}Asegúrate de que el read timeout del proxy sea mayor que tu intervalo de keep-alive de MQTT; si no, el proxy cierra las conexiones inactivas y los clientes sufren desconexiones inexplicables.
Checklist de resolución de problemas
- ¿Usas el puerto 1883 u 8883 desde un navegador? Son puertos TCP: cambia al listener WebSocket.
- ¿Página en HTTPS y la URL empieza por
ws://? Contenido mixto: usawss://. - ¿El handshake falla de inmediato? Revisa la ruta (
/mqtto ninguna) y el subprotocolo. - ¿Detrás de un reverse proxy? Nginx y proxies similares deben reenviar las cabeceras
UpgradeyConnectiony permitir conexiones de larga duración. - ¿Conectado pero te expulsan? Es un problema a nivel de MQTT: consulta errores de conexión MQTT.
Prueba ahora una conexión wss://
Preguntas frecuentes
¿Por qué mi navegador no puede conectarse a MQTT en el puerto 1883?
Los navegadores no pueden abrir sockets TCP directos, y el puerto 1883 habla MQTT puro sobre TCP. Un cliente de navegador debe conectarse al listener WebSocket del broker, usando una URL ws:// o wss:// con el puerto y la ruta correctos.
¿Qué diferencia hay entre ws y wss en MQTT?
ws:// es una conexión WebSocket sin cifrar; wss:// es WebSocket sobre TLS. Las páginas servidas por HTTPS solo pueden usar wss://, y wss:// es la única opción sensata para cualquier cosa que no sean pruebas locales.
¿Qué ruta debo usar para MQTT sobre WebSockets?
Depende del broker. Muchos brokers, como EMQX y HiveMQ, usan /mqtt. Mosquitto acepta conexiones sea cual sea la ruta. Revisa la documentación del broker; una ruta incorrecta suele fallar durante el handshake de WebSocket.