Broker Testing

How to Test MQTT over WebSockets (ws and wss)

Test an MQTT WebSocket endpoint step by step: the /mqtt path, the mqtt subprotocol, curl and wscat handshake checks, browser DevTools and an MQTT.js test.

Updated · 8 min read

How to Test MQTT over WebSockets (ws and wss)

To test MQTT over WebSockets, verify the connection in two stages: first the HTTP upgrade (right URL, right path, mqtt subprotocol, 101 Switching Protocols), then a real MQTT session inside the socket (CONNECT, CONNACK, publish, subscribe). Most WebSocket problems — 404s, closed sockets, mixed-content blocks — happen in the first stage, before MQTT is even involved.

The examples use broker.connectmq.com: ws:// on port 8083 and wss:// on port 8084, both with the path /mqtt.

What a working MQTT WebSocket connection looks like

An MQTT WebSocket URL has four parts, and each one can be wrong:

PartExampleTypical mistake
Schemewss://ws:// from an HTTPS page (blocked as mixed content)
Hostbroker.connectmq.comIP address with wss — certificate mismatch
Port8084Using the TCP port 1883 or TLS port 8883
Path/mqttMissing or wrong path — 404 Not Found

The client also sends the header Sec-WebSocket-Protocol: mqtt. The broker must answer with HTTP/1.1 101 Switching Protocols and echo Sec-WebSocket-Protocol: mqtt. After that, the socket carries ordinary binary MQTT packets — the same CONNECT and CONNACK as over TCP. For the background, see MQTT over WebSockets.

Stage 1: test the handshake

With curl

curl can send the upgrade request by hand and show you the raw response. That isolates the HTTP part from any MQTT library:

WebSocket handshake check
curl -i --http1.1 --max-time 5 \
  -H "Connection: Upgrade" \
  -H "Upgrade: websocket" \
  -H "Sec-WebSocket-Version: 13" \
  -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
  -H "Sec-WebSocket-Protocol: mqtt" \
  https://broker.connectmq.com:8084/mqtt

Use http://...:8083/mqtt to test the unencrypted listener. A healthy endpoint answers:

Expected response
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: mqtt

curl then waits until --max-time because the socket stays open — that is expected. Other responses tell you what to fix:

ResponseMeaning
101 without Sec-WebSocket-Protocol: mqttBroker or proxy drops the subprotocol; browsers will close the socket
404 Not FoundWrong path — try /mqtt, / or your broker's documented path (details)
400 Bad Request / 426 Upgrade RequiredUpgrade headers missing — often a proxy that does not forward them
502 Bad GatewayReverse proxy cannot reach the broker's WebSocket listener
Empty reply from server or resetPort is plain MQTT/TLS, not WebSocket
SSL certificate errorSame TLS issues as on 8883 — the certificate must match the hostname

If the broker sits behind Nginx or another proxy, 400 and a missing subprotocol almost always mean the Upgrade and Connection headers are not passed through — see MQTT behind an Nginx proxy.

With wscat

wscat is a small WebSocket client that negotiates the subprotocol properly:

wscat
npx wscat -c wss://broker.connectmq.com:8084/mqtt -s mqtt

Connected (press CTRL+C to quit) means the handshake and subprotocol are fine. After a few seconds the broker closes the socket, because no MQTT CONNECT packet arrived — this is normal and actually confirms an MQTT broker is listening. Without -s mqtt, many brokers refuse the upgrade, which is a useful negative test.

Stage 2: test a real MQTT session

The mosquitto command-line clients cannot connect over WebSockets, so use a WebSocket-capable client. The quickest is the online MQTT client: enter the wss:// URL, connect, subscribe to a test topic and publish to it. If that works, the broker side is correct and any remaining problem is in your application.

For an automated check, this MQTT.js 5 script runs in Node.js or, almost unchanged, in the browser:

ws-check.mjs (MQTT.js 5)
import mqtt from "mqtt";

const url = "wss://broker.connectmq.com:8084/mqtt";
const topic = `testmqtt/ws/${Math.random().toString(16).slice(2, 10)}`;

const client = mqtt.connect(url, {
  protocolVersion: 5,
  username: process.env.MQTT_USER,
  password: process.env.MQTT_PASS,
  connectTimeout: 5000,
  reconnectPeriod: 0, // fail fast while testing
});

const started = Date.now();
client.on("connect", (connack) => {
  console.log(`CONNACK reason ${connack.reasonCode ?? 0} after ${Date.now() - started} ms`);
  client.subscribe(topic, { qos: 1 }, () => client.publish(topic, "ping over websocket", { qos: 1 }));
});
client.on("message", (t, payload) => {
  console.log(`round trip ok on ${t}: ${payload}`);
  client.end();
});
client.on("error", (err) => { console.error("error:", err.message); process.exit(1); });
client.on("close", () => console.log("socket closed"));

MQTT.js sets the mqtt subprotocol automatically. Python works too — paho-mqtt 2.x with transport="websockets":

ws_check.py (paho-mqtt 2.x)
import os
import paho.mqtt.client as mqtt

c = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, transport="websockets", protocol=mqtt.MQTTv5)
c.ws_set_options(path="/mqtt")
c.tls_set()  # remove for ws:// on port 8083
c.username_pw_set(os.environ["MQTT_USER"], os.environ["MQTT_PASS"])
c.on_connect = lambda cl, u, f, rc, p: print("CONNACK:", rc)
c.connect("broker.connectmq.com", 8084)
c.loop_forever()

paho's default WebSocket path is already /mqtt, so ws_set_options is optional here — but brokers that use / or a custom path answer with 404 until you set it explicitly.

Debugging in browser DevTools

When a web app cannot connect, the browser shows you every detail of the handshake:

  1. Open DevTools → Network, filter by WS (Chrome) or WS in the type filter (Firefox), and reload the page.
  2. Click the socket request. Under Headers, check the status (101), the request URL and path, and that both request and response include Sec-WebSocket-Protocol: mqtt.
  3. Open the Messages tab (Chrome) or Response (Firefox). The first outgoing frame is the binary MQTT CONNECT; the first incoming frame is the CONNACK. If CONNECT goes out and the socket closes without a CONNACK, the broker rejected the MQTT session (credentials, client ID) rather than the WebSocket.
  4. Check the Console for errors like these:
Typical console errors
WebSocket connection to 'wss://broker.example.com:8084/mqtt' failed:
Mixed Content: The page at 'https://app.example.com/' was loaded over HTTPS, but attempted to connect to the insecure WebSocket endpoint 'ws://broker.example.com:8083/mqtt'. This request has been blocked; this endpoint must be available over WSS.

The first, with nothing after the colon, is the browser's generic failure — usually wrong port, path or certificate; check the Network tab. The second is fixed only by switching to wss:// (mixed content). A close code 1006 means the connection dropped without a proper close frame, often a proxy timeout or the broker refusing the connection. For the full list of causes, see MQTT WebSocket connection failed.

WebSocket test checklist

  1. curl to the wss:// URL returns 101 and echoes Sec-WebSocket-Protocol: mqtt.
  2. wscat with -s mqtt connects; without it the broker rejects or closes.
  3. A real client gets a CONNACK and completes a publish/subscribe round trip.
  4. The browser connects from an HTTPS page with wss://, with no console errors.
  5. Through your proxy or load balancer, idle connections survive longer than the keep-alive interval.
  6. Credentials and ACLs behave the same on the WebSocket listener as on TCP.

Test the WebSocket endpoint in your browser

WebSocket issues look mysterious only until you split them in two. Prove the HTTP upgrade with curl or wscat, then prove the MQTT session with a real client, and you will know within minutes whether to fix the URL, the proxy or the MQTT credentials.

Frequently asked questions

What is the WebSocket path for MQTT?

There is no single standard. /mqtt is the most common (EMQX, HiveMQ and ConnectMQ use it, and proxies in front of Mosquitto usually route it), but some brokers listen on / or a custom path. A wrong path usually returns HTTP 404 during the handshake.

Which WebSocket subprotocol does MQTT use?

mqtt, for both MQTT 3.1.1 and MQTT 5. Old MQTT 3.1 clients used mqttv3.1. The client sends it in Sec-WebSocket-Protocol and the broker must echo it in the 101 response; browsers drop the connection if it does not.

Can mosquitto_pub connect over WebSockets?

No. The mosquitto command-line clients only speak MQTT over TCP and TLS. Use a browser client, MQTT.js, paho-mqtt with transport="websockets", or a desktop client such as MQTTX to test a WebSocket listener.