MQTT sobre WebSocket permite que uma página web publique e assine em um broker MQTT diretamente do navegador, sem nenhum backend intermediário. Neste tutorial você vai entender por que os navegadores precisam do transporte WebSocket, a diferença entre ws e wss e como construir uma página HTML completa e um cliente Node.js que conversam com o servidor MQTT público (gratuito) broker.connectmq.com.
Novo em brokers? Leia primeiro O Que É um Broker MQTT? para os fundamentos de publish/subscribe.
Por Que os Navegadores Precisam de MQTT sobre WebSocket
O MQTT nativo roda sobre uma conexão TCP simples, geralmente na porta 1883 (ou 8883 com TLS). Aplicativos desktop, servidores e microcontroladores podem abrir sockets TCP livremente. Navegadores não: por questões de segurança, o JavaScript de uma página web só tem acesso a HTTP, WebSocket e alguns outros transportes gerenciados pelo navegador.
O WebSocket resolve isso. Ele começa como uma requisição HTTP comum com o cabeçalho Upgrade: websocket e, quando o servidor responde 101 Switching Protocols, a conexão vira um canal binário full-duplex. Os pacotes MQTT (CONNECT, PUBLISH, SUBSCRIBE etc.) são então transportados sem alterações dentro de frames WebSocket. O cliente pede o subprotocolo mqtt durante o handshake para que o broker saiba o que esperar.
Browser (MQTT.js) broker.connectmq.com:8084
|-- TLS handshake ------------------------------>|
|-- GET /mqtt Upgrade: websocket |
| Sec-WebSocket-Protocol: mqtt ---------------->|
|<-- 101 Switching Protocols ---------------------|
|== WebSocket frame [ MQTT CONNECT ] ============>|
|<= WebSocket frame [ MQTT CONNACK ] =============|
|== WebSocket frame [ MQTT SUBSCRIBE ] ==========>|
|<= WebSocket frame [ MQTT PUBLISH ] =============|
Do ponto de vista do broker, um cliente WebSocket é só mais um cliente MQTT. Ele pode conversar com dispositivos conectados via TCP, compartilhar topics com eles e usar os mesmos níveis de QoS, retained messages e recursos do MQTT 5.
Usos típicos de MQTT sobre WebSocket:
- Dashboards IoT ao vivo que se atualizam no instante em que um sensor publica
- Painéis de controle no navegador que enviam comandos para dispositivos
- Ferramentas de monitoramento e gêmeos digitais que mostram o estado das máquinas em tempo real
- Aplicativos móveis híbridos construídos com tecnologias web
ws vs wss: Portas e Caminhos no ConnectMQ
Assim como HTTP e HTTPS, o WebSocket tem uma forma sem criptografia (ws://) e uma criptografada com TLS (wss://).
| Transporte | Esquema | Porta | URL completa | Quando usar |
|---|---|---|---|---|
| MQTT (TCP) | mqtt:// | 1883 | mqtt://broker.connectmq.com:1883 | Dispositivos, servidores, CLI |
| MQTT sobre TLS | mqtts:// | 8883 | mqtts://broker.connectmq.com:8883 | Dispositivos e servidores em produção |
| WebSocket | ws:// | 8083 | ws://broker.connectmq.com:8083/mqtt | Testes locais a partir de uma página http:// |
| WebSocket seguro | wss:// | 8084 | wss://broker.connectmq.com:8084/mqtt | Navegadores e aplicações web em produção |
Dois detalhes importam:
- O caminho é
/mqtt. Uma URL WebSocket é uma URL HTTP, então ela tem um caminho. Conectar emwss://broker.connectmq.com:8084sem/mqttfaz o handshake falhar. - Prefira wss. Páginas servidas via HTTPS só podem abrir conexões
wss://, e credenciais enviadas viaws://puro trafegam em texto claro. O ConnectMQ oferece WSS na porta 8084 com um certificado Let's Encrypt, então os navegadores confiam nele sem configuração extra.
Construa uma Página com MQTT sobre WebSocket usando MQTT.js
A página a seguir é completa e autocontida. Salve-a como index.html e abra-a em um navegador. Ela carrega o MQTT.js de uma CDN, conecta anonimamente à área pública do broker.connectmq.com, assina um topic único e permite publicar mensagens.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>MQTT over WebSocket demo</title>
<script src="https://unpkg.com/mqtt@5.10.3/dist/mqtt.min.js"></script>
</head>
<body>
<p>Status: <span id="status">connecting...</span></p>
<input id="msg" value="Hello from the browser">
<button id="send">Publish</button>
<pre id="log"></pre>
<script>
const id = Math.random().toString(16).slice(2, 10);
const clientId = "web-" + id;
const topic = "connectmq/demo/" + id + "/chat";
const log = (line) => {
document.getElementById("log").textContent += line + "\n";
};
const client = mqtt.connect("wss://broker.connectmq.com:8084/mqtt", {
clientId: clientId,
protocolVersion: 5,
clean: true,
keepalive: 30,
reconnectPeriod: 2000,
connectTimeout: 10000
});
client.on("connect", () => {
document.getElementById("status").textContent = "connected as " + clientId;
client.subscribe(topic, { qos: 1 }, (err, granted) => {
if (err) return log("Subscribe error: " + err.message);
log("Subscribed to " + granted[0].topic + " (QoS " + granted[0].qos + ")");
});
});
client.on("message", (t, payload) => {
log("[" + t + "] " + payload.toString());
});
client.on("reconnect", () => {
document.getElementById("status").textContent = "reconnecting...";
});
client.on("error", (err) => log("Error: " + err.message));
document.getElementById("send").onclick = () => {
const text = document.getElementById("msg").value;
client.publish(topic, text, { qos: 1 });
};
</script>
</body>
</html>
Ao clicar em Publish, a mensagem vai para o broker pelo WebSocket seguro e volta direto pela sua própria assinatura. Abra a página em duas abas e você verá que cada aba ganha seu próprio topic aleatório, então uma não vê a outra. Para compartilhar um topic, fixe o mesmo topic connectmq/demo/<your-id>/chat nas duas.
Algumas observações sobre as opções:
clientIdprecisa ser não vazio e único. A área pública rejeita um client ID vazio, e dois clientes com o mesmo ID ficam derrubando um ao outro.protocolVersion: 5usa MQTT 5. Use4para MQTT 3.1.1, se precisar.keepalive: 30faz o cliente enviar um PINGREQ quando a conexão está ociosa, para que o broker e eventuais proxies saibam que ela continua viva.reconnectPeriod: 2000faz o MQTT.js reconectar automaticamente dois segundos após uma queda.
Você também pode acompanhar as mensagens do navegador em um terminal com o CLI do Mosquitto, porque clientes TCP e WebSocket compartilham os mesmos topics. Troque abc12345 pelo id exibido no topic da página:
mosquitto_sub -h broker.connectmq.com -p 1883 -i cli-watch-abc12345 -t "connectmq/demo/abc12345/#" -v
MQTT sobre WebSocket no Node.js
O Node.js pode usar TCP puro, mas WebSocket é útil quando seu servidor fica atrás de um proxy ou firewall que só permite tráfego no estilo HTTPS, ou quando você quer compartilhar código com uma aplicação de navegador. Instale o MQTT.js com npm install mqtt e salve isto como ws-client.mjs:
import mqtt from "mqtt";
const id = Math.random().toString(16).slice(2, 10);
const clientId = `node-${id}`;
const topic = `connectmq/demo/${id}/hello`;
const client = mqtt.connect("wss://broker.connectmq.com:8084/mqtt", {
clientId,
protocolVersion: 5,
keepalive: 30,
});
client.on("connect", (connack) => {
console.log("Connected, CONNACK properties:", connack.properties);
client.subscribe(topic, { qos: 1 }, (err, granted) => {
if (err) throw err;
console.log("Granted:", granted);
client.publish(topic, JSON.stringify({ temp: 21.5, ts: Date.now() }), { qos: 1 });
});
});
client.on("message", (t, payload) => {
console.log(`Received on ${t}: ${payload.toString()}`);
client.end();
});
client.on("error", (err) => {
console.error("Connection error:", err.message);
client.end();
});
Execute com node ws-client.mjs. A saída fica parecida com esta:
Connected, CONNACK properties: { receiveMaximum: 16, topicAliasMaximum: 65535 }
Granted: [ { topic: 'connectmq/demo/4df8521c/hello', qos: 1, nl: false, rap: false, rh: 0 } ]
Received on connectmq/demo/4df8521c/hello: {"temp":21.5,"ts":1790000000000}
A única diferença em relação a um cliente TCP é a URL. Troque-a por mqtt://broker.connectmq.com:1883 e o resto do código continua igual. Para entender os valores de qos acima, veja Níveis de QoS MQTT Explicados.
Solução de Problemas em Conexões MQTT sobre WebSocket
A maioria dos problemas aparece como uma conexão que nunca chega ao connect ou que fica reconectando sem parar. Abra as ferramentas de desenvolvedor do navegador, confira o Console e a aba Network (filtre por WS) e compare com esta tabela.
| Sintoma | Causa provável | Solução |
|---|---|---|
| Erro "Mixed Content", conexão bloqueada | Uma página https:// está tentando abrir ws:// | Use wss://broker.connectmq.com:8084/mqtt |
| Handshake falha, HTTP 404 na aba Network | Caminho ausente ou incorreto | Adicione /mqtt à URL |
| Handshake falha na 8083 ou 8084 | Esquema e porta trocados (ws na 8084, wss na 8083) | ws vai com 8083, wss com 8084 |
| Você suspeita de um problema de CORS | WebSocket não é regido por CORS como fetch/XHR | Procure a causa real: URL, porta, TLS ou credenciais |
| Conecta e desconecta a cada poucos segundos | Dois clientes usando o mesmo client ID | Gere um ID único por aba ou dispositivo |
| Conexão recusada logo após o CONNECT | Client ID vazio ou usuário/senha incorretos | Defina um client ID não vazio; confira as credenciais do portal |
| Cai após um período de inatividade | Keepalive longo demais para um proxy ou rede móvel | Use keepalive: 30 ou menos |
| Funciona localmente, falha na rede corporativa | Proxy ou firewall bloqueia portas não padrão | Teste a partir de outra rede ou peça à TI para liberar tráfego de saída para a porta 8084 |
Dois desses merecem um olhar mais atento.
Mixed content
Navegadores bloqueiam sub-recursos inseguros em páginas seguras. Se o seu dashboard é servido via https://, qualquer conexão ws:// é recusada antes de sair do navegador, e o broker nem chega a vê-la. A solução é sempre wss://. O endpoint sem criptografia ws://broker.connectmq.com:8083/mqtt só é útil para páginas abertas a partir de http://localhost ou de um arquivo local.
Conflitos de client ID
O MQTT permite apenas uma conexão por client ID. Quando chega uma segunda conexão com o mesmo ID, o broker fecha a primeira. No navegador, isso costuma acontecer quando um usuário abre a mesma página em duas abas e o client ID está fixo no código. Com a reconexão automática ativada, as duas abas brigam pelo ID para sempre. Acrescente um sufixo aleatório por aba. Em um broker ConnectMQ privado, os client IDs ficam restritos ao client ID emitido e às suas variantes com _sufixo, então use YOUR_CLIENT_ID_tab1, YOUR_CLIENT_ID_tab2 e assim por diante.
Produção: Usando as Credenciais do Portal
A área pública serve para testes: tudo o que é publicado ali pode ser lido por qualquer pessoa, e os limites são apertados (64 KB por mensagem, 10 mensagens por segundo por cliente). Para uma aplicação real, crie no portal um broker MQTT gerenciado privado e conecte com as credenciais geradas:
const client = mqtt.connect("wss://broker.connectmq.com:8084/mqtt", {
clientId: "YOUR_CLIENT_ID_dashboard",
username: "YOUR_USERNAME",
password: "YOUR_PASSWORD",
protocolVersion: 5,
keepalive: 30,
});
Tenha estes pontos em mente em aplicações de navegador:
- Sempre use wss em produção. O usuário e a senha são enviados no pacote CONNECT, então o transporte precisa ser criptografado.
- Considere as credenciais do navegador como visíveis. Tudo o que é entregue ao navegador pode ser lido pelo usuário. Dê à aplicação web suas próprias credenciais em vez de reutilizar as dos dispositivos e evite incorporá-las em páginas públicas.
- Respeite os limites do plano. Cada aba aberta é uma conexão. O plano Serverless (Free) permite até 25 conexões simultâneas, o que serve para protótipos. Dashboards de produção se encaixam melhor no Serverless (Paid), com até 1.000 conexões, ou em um cluster Dedicated.
- Trate reconexões com elegância. Mostre um indicador de status e reassine os topics ou conte com uma sessão persistente quando a conexão voltar.
Mais conselhos de proteção estão reunidos em Segurança MQTT: Boas Práticas.
Comece com o ConnectMQ
Você pode testar MQTT sobre WebSocket agora mesmo com o endpoint do servidor MQTT público wss://broker.connectmq.com:8084/mqtt, feito para testes. Quando estiver pronto para construir um dashboard de verdade, cadastre-se gratuitamente em app.connectmq.com (plano Serverless gratuito, sem cartão de crédito), crie um broker e conecte com seu próprio usuário, senha e client ID. O cliente MQTT integrado ao navegador e o explorador de topics ao vivo do portal facilitam verificar se sua aplicação web está publicando o que você espera.
Perguntas frequentes
Qual porta o MQTT sobre WebSocket usa?
Não existe uma única porta padrão. No broker.connectmq.com, o WebSocket sem criptografia usa a porta 8083 e o WebSocket seguro usa a porta 8084, ambos com o caminho /mqtt.
MQTT sobre WebSocket é mais lento do que MQTT sobre TCP?
O overhead é pequeno. Depois do upgrade HTTP inicial, cada pacote MQTT é envolvido em um frame WebSocket que acrescenta só alguns bytes, então a latência fica próxima da do TCP puro para a maioria das aplicações.
Preciso de um servidor backend para usar MQTT no navegador?
Não. Com MQTT sobre WebSocket, o navegador se conecta diretamente ao broker. Um backend só é necessário se você quiser manter as credenciais fora do cliente ou adicionar sua própria lógica de negócio.
O CORS se aplica ao MQTT sobre WebSocket?
Não, conexões WebSocket não passam por verificações de preflight de CORS como as requisições fetch e XHR. Se uma conexão falhar, verifique a URL, a porta, o caminho e as credenciais.
Clientes no navegador e dispositivos ESP32 podem compartilhar os mesmos topics?
Sim. O broker roteia mensagens por topic independentemente do transporte, então um navegador via WSS e um dispositivo via TCP na porta 1883 podem publicar e assinar os mesmos topics. Veja o tutorial de ESP32 com MQTT para o lado do dispositivo.



