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:
| Part | Example | Typical mistake |
|---|---|---|
| Scheme | wss:// | ws:// from an HTTPS page (blocked as mixed content) |
| Host | broker.connectmq.com | IP address with wss — certificate mismatch |
| Port | 8084 | Using the TCP port 1883 or TLS port 8883 |
| Path | /mqtt | Missing 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:
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/mqttUse http://...:8083/mqtt to test the unencrypted listener. A healthy endpoint answers:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: mqttcurl then waits until --max-time because the socket stays open — that is expected. Other responses tell you what to fix:
| Response | Meaning |
|---|---|
101 without Sec-WebSocket-Protocol: mqtt | Broker or proxy drops the subprotocol; browsers will close the socket |
404 Not Found | Wrong path — try /mqtt, / or your broker's documented path (details) |
400 Bad Request / 426 Upgrade Required | Upgrade headers missing — often a proxy that does not forward them |
502 Bad Gateway | Reverse proxy cannot reach the broker's WebSocket listener |
Empty reply from server or reset | Port is plain MQTT/TLS, not WebSocket |
| SSL certificate error | Same 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:
npx wscat -c wss://broker.connectmq.com:8084/mqtt -s mqttConnected (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:
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":
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:
- Open DevTools → Network, filter by WS (Chrome) or WS in the type filter (Firefox), and reload the page.
- Click the socket request. Under Headers, check the status (
101), the request URL and path, and that both request and response includeSec-WebSocket-Protocol: mqtt. - 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.
- Check the Console for errors like these:
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
- curl to the
wss://URL returns101and echoesSec-WebSocket-Protocol: mqtt. - wscat with
-s mqttconnects; without it the broker rejects or closes. - A real client gets a CONNACK and completes a publish/subscribe round trip.
- The browser connects from an HTTPS page with
wss://, with no console errors. - Through your proxy or load balancer, idle connections survive longer than the keep-alive interval.
- 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.


