MQTT Over WebSockets Explained: Browsers, Ports, TLS

By | June 14, 2026

MQTT runs on top of TCP. That works well almost everywhere it is deployed: on embedded devices, gateways, servers, and desktop tools. There is one environment, though, where it does not work directly: the browser. A web page cannot open a raw TCP connection, which means a JavaScript MQTT client cannot connect to a broker over plain MQTT the way every other client can. MQTT over WebSockets is the solution. It wraps MQTT control packets inside WebSocket frames so that a browser, using only the network primitives it does have, can act as a full MQTT client.

This article explains how MQTT over WebSockets works on the wire, how the WebSocket handshake negotiates MQTT as a subprotocol, which ports it uses, how secure WebSockets (wss://) layer TLS underneath, what is the same and what is different about a browser-based MQTT client compared to a native one, and the practical considerations of running it in production. It is a technical reference; the connection handshake, security, and packet structure each have their own dedicated articles in this category.

MQTT over WebSockets at a glance

AspectDetail
Underlying transportWebSocket (which itself runs on TCP)
Spec for WebSocketRFC 6455
URL schemesws:// (unencrypted), wss:// (TLS-encrypted)
Default ports80 (ws://), 443 (wss://) — same as HTTP/HTTPS
Subprotocol negotiationSec-WebSocket-Protocol header
MQTT subprotocol valuesmqtt (current), mqttv3.1 (older, still seen)
MQTT features supportedAll of them — QoS, sessions, retained messages, LWT, keep alive
Broker requirementNative WebSocket support, or a separate front-end that bridges to MQTT
Browser supportAll modern browsers support WebSockets

Why you can’t speak raw MQTT in a browser

The reason MQTT over WebSockets exists is mundane and important: browsers do not expose raw TCP sockets to JavaScript. A web page can send HTTP requests via fetch or XMLHttpRequest, open WebSocket connections via the WebSocket API, and use a few other higher-level abstractions, but it cannot open a bare TCP connection to an arbitrary host and port. That is a deliberate security design; allowing arbitrary TCP connections from any web page would create severe cross-origin and port-scanning risks.

Native MQTT, however, is exactly a TCP-based protocol. The MQTT spec assumes the client has a TCP/IP stack and can open a connection to the broker’s MQTT port (1883 plain, 8883 with TLS). A browser has neither the ability nor the permission to do that. There is no way for a browser to send a raw CONNECT packet over TCP, no matter how much JavaScript is willing to do.

WebSockets, standardized as RFC 6455 in 2011, are the bridge. A WebSocket is a long-lived, full-duplex, bidirectional channel between a browser and a server, established by starting as an HTTP request and then upgrading to the WebSocket protocol. Once established, a WebSocket carries arbitrary binary frames between the two sides. That is enough to carry MQTT packets, and it is the only mechanism a browser has for the kind of persistent, push-capable connection MQTT needs.

(There is a Sockets API in development that would, in theory, give browsers access to TCP and UDP, but it is not widely implemented, and using WebSockets remains the practical and standard approach.)

How MQTT travels over WebSockets

The wrapping is straightforward and, importantly, does not change MQTT itself. An MQTT control packet, with its fixed header, optional variable header, and optional payload, is constructed exactly as it would be over plain TCP. That entire packet is then carried as the payload of a WebSocket binary frame. WebSocket framing handles delivery; MQTT handles meaning.

Several reasons make WebSockets a clean fit for MQTT:

  • WebSockets are bidirectional, like the long-lived TCP connection that native MQTT uses. The broker can push messages down to the client without the client polling.
  • WebSockets are ordered and lossless, because they ride on TCP. MQTT’s QoS guarantees do not have to be re-engineered to deal with reordering or loss at the transport layer.
  • WebSockets carry arbitrary binary frames, so the binary nature of MQTT’s packet format is preserved exactly. There is no need to convert MQTT to text or base64 or any other transit format.
  • WebSocket frames are cheap, so the per-frame overhead added to each MQTT packet is small.

A few points of nuance are worth understanding. A single WebSocket frame is not required to contain exactly one MQTT packet. The MQTT packet boundary is encoded in the MQTT fixed header (with its remaining-length field), not in the WebSocket framing, so an MQTT library on top of a WebSocket has to read bytes from incoming frames and reassemble them into MQTT packets. In practice this is handled by the MQTT-over-WebSockets client library; the developer using it sees the same client API as a native MQTT library.

Also, while WebSockets support both binary and text frames, MQTT-over-WebSockets uses binary frames, because MQTT packets are binary and not all bytes are valid UTF-8. A client or broker that sent MQTT in WebSocket text frames would be sending malformed text and likely break.

The WebSocket handshake and the MQTT subprotocol

A WebSocket connection does not start as a WebSocket. It starts as an ordinary HTTP request that asks to be upgraded. The client sends a GET request to the server with several special headers, the most distinctive being Upgrade: websocket and Connection: Upgrade. If the server agrees, it responds with HTTP status 101 (“Switching Protocols”), and from that point on the connection is a WebSocket carrying frames instead of HTTP requests.

This handshake is also where the application protocol on top of WebSockets is negotiated, through a header called Sec-WebSocket-Protocol. WebSockets are deliberately a generic transport, so they let the client and server agree on which subprotocol they will speak inside the frames. The client lists the subprotocols it understands, and the server picks one (or rejects the handshake if it cannot agree on any of them).

For MQTT, two subprotocol values are used:

  • mqtt — the current standard identifier, used for MQTT over WebSockets.
  • mqttv3.1 — an older identifier that some implementations still send for backward compatibility.

A browser MQTT client typically lists both in Sec-WebSocket-Protocol, in preference order, when it opens the WebSocket. The broker echoes back whichever it has chosen in its 101 response, and both sides then know they are speaking MQTT inside the WebSocket frames.

It is worth being clear about something the Sec-WebSocket-Protocol header name often misleads people about: the subprotocol negotiates the application-layer message format only. It says nothing about the TCP port the WebSocket is on or about any lower-level transport choice. A WebSocket on port 443 with Sec-WebSocket-Protocol: mqtt is still a WebSocket on port 443; the subprotocol just tells the two ends to interpret the frame payloads as MQTT.

A worked trace of the upgrade handshake

To make the handshake concrete, here is what the bytes look like on the wire when a browser-based MQTT client opens a WebSocket to a broker.

Client request (sent over the TCP/TLS connection):

GET /mqtt HTTP/1.1
Host: broker.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Sec-WebSocket-Protocol: mqtt, mqttv3.1
Origin: https://app.example.com

The key fields are Upgrade: websocket and Connection: Upgrade (asking for the upgrade), Sec-WebSocket-Version: 13 (the RFC 6455 version, the only one in real use), Sec-WebSocket-Key (a random base64-encoded value the server will hash to prove it understood the handshake), and Sec-WebSocket-Protocol listing the MQTT subprotocols the client supports.

Server response:

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

The 101 Switching Protocols status is the cue that the upgrade succeeded. Sec-WebSocket-Accept is the hashed-and-encoded response to the client’s key, which the client verifies. Sec-WebSocket-Protocol: mqtt tells the client that the broker has chosen mqtt from the list the client offered, so both sides now agree on the subprotocol.

From the next byte onward, the connection is no longer HTTP. It is a stream of WebSocket frames, and inside those frames are MQTT control packets. The first thing the client typically sends is a WebSocket binary frame whose payload is an MQTT CONNECT packet; the broker responds with a frame whose payload is the CONNACK. From there, the conversation is ordinary MQTT, just with each packet wrapped in a frame.

If anything fails along the way — wrong path, unsupported subprotocol, mixed-content rules, TLS errors — the connection never reaches the MQTT layer at all, and the failure looks like a WebSocket or HTTP error rather than an MQTT error. That is why debugging MQTT over WebSockets often starts at the HTTP and WebSocket layer rather than at MQTT itself.

Default ports, and why they matter

Native MQTT uses port 1883 for plain connections and 8883 for TLS. MQTT over WebSockets uses very different ports, because WebSockets piggyback on HTTP:

  • Port 80 for unencrypted WebSockets (ws://).
  • Port 443 for TLS-encrypted WebSockets (wss://).

These are the same ports as HTTP and HTTPS. From a network’s point of view, an MQTT-over-WebSockets connection looks like an outbound HTTPS connection until it upgrades. That is a feature, not a bug. Many corporate and public networks block traffic on non-standard ports while allowing ports 80 and 443 freely. A native MQTT client trying to reach a broker on port 8883 may be blocked outright by such a network, where the same broker reached via wss:// on port 443 sails through. For deployments where clients have to traverse restrictive networks, MQTT over WebSockets on port 443 is often the only way to make MQTT work at all.

The ports above are conventions, not requirements. Brokers and clients can be configured to use any port for WebSockets, just as native MQTT can use any port. But matching the HTTP and HTTPS defaults is the standard practice, because it maximizes compatibility with networks and proxies.

Secure WebSockets: wss:// and TLS

The same TLS that secures HTTPS secures wss://. The browser opens a TLS connection on port 443 to the server, completes the TLS handshake, and then performs the WebSocket upgrade inside the encrypted tunnel. Every byte of the WebSocket, including all the MQTT packets it carries, is encrypted at the transport layer. This is the recommended setup for anything exposed beyond a trusted network, and in browsers especially, because most browsers refuse to open plain ws:// connections from pages served over HTTPS (the so-called “mixed content” rule) and may show security warnings when a page mixes secure and insecure connections.

A few practical considerations come with wss://:

  • The broker, or the proxy in front of it, must terminate TLS for the WebSocket. This is just standard TLS configuration on the server side, the same as for any HTTPS service.
  • Certificate handling is the browser’s, not your code’s. The browser validates the server’s certificate against its trust store; a self-signed certificate will be rejected unless the user explicitly trusts it.
  • Client certificates for mutual TLS work, but browser support varies and the user experience is poor; username/password (over TLS) or token-based authentication is the typical choice for browser clients.

The security article in this category covers the broader MQTT security model. For browser clients, the rule of thumb is simple: use wss://, not ws://, for anything that is not a strictly local test.

What changes for the browser client, and what doesn’t

A major point that is sometimes lost: a browser running MQTT over WebSockets is a full MQTT client, not a stripped-down version. Every MQTT feature works as it does for a native client. Keep alive, Last Will and Testament, Quality of Service levels, retained messages, persistent sessions, MQTT 5 properties — all of them apply unchanged, because the protocol on top of the WebSocket is exactly the same MQTT protocol.

What does change is everything below MQTT:

  • The transport is WebSocket frames over TCP, not raw TCP. From MQTT’s perspective this is transparent; from an operational perspective it changes the ports, the URL schemes, and the proxy and load-balancer configurations involved.
  • The client implementation is a JavaScript library that uses the browser’s WebSocket API. Common libraries include Eclipse Paho’s JavaScript client, MQTT.js, and similar projects. They expose the familiar MQTT API (connect, publish, subscribe, callbacks) and handle the WebSocket framing internally.
  • The deployment environment. Browser clients usually live behind the same kinds of HTTPS-fronted infrastructure as web applications: load balancers, reverse proxies, content delivery networks. The MQTT broker has to be reachable through that infrastructure, which is usually done by terminating TLS at a proxy and forwarding the upgraded WebSocket connection to the broker.

What does not change is the broker’s view of the client. Once the WebSocket is established and MQTT is negotiated as the subprotocol, the broker treats the browser client as it would any other MQTT client: the same CONNECT, the same subscriptions, the same delivery rules. From the broker’s perspective, the WebSocket framing is the transport, and that is the broker’s only concern with it.

Browser support and library landscape

Every modern browser supports WebSockets natively. They have been a standard part of the web platform for over a decade, and there is no browser that a current MQTT-over-WebSockets client realistically targets that does not support them. For unusually old environments, polyfill libraries exist that provide WebSocket-like APIs over older transports such as long polling, though those are increasingly rare in practice.

On the broker side, support for native WebSockets is now the norm. The conventional architecture is for the broker itself to listen on a WebSocket port (typically 443 for wss://) and negotiate the MQTT subprotocol on the upgrade handshake. Some deployments place a web server or reverse proxy in front of the broker to terminate TLS and forward the WebSocket to the broker over a private network. A few historic setups bridged HTTP/WebSocket on one side to native MQTT on the other through a separate process; this is generally not recommended, because the bridge is an extra moving part with its own scaling and reliability concerns, and brokers with first-class WebSocket support remove the need.

The MQTT client libraries used in browsers handle the WebSocket setup transparently. From the developer’s perspective, the client API looks the same as a native MQTT library: open a connection with a URL such as wss://broker.example.com/mqtt, attach event handlers, publish, subscribe. The library packages each MQTT packet into a WebSocket binary frame and parses incoming frames back into MQTT packets, without the application code seeing any of it.

When MQTT over WebSockets is the right choice

The clearest case is the browser. Whenever the MQTT client needs to live in a web page, MQTT over WebSockets is the only realistic option. That covers a wide range of use cases: live dashboards that display real-time device data, web applications that send push notifications to users, browser-based monitoring tools, mobile web apps that need a persistent server connection without polling, and any other browser interface that has to interact with MQTT-based systems.

Beyond browsers, MQTT over WebSockets is also useful in any environment where a native MQTT port is hard to reach. As covered earlier, networks that block non-standard ports often allow ports 80 and 443, so running MQTT over WebSockets on port 443 makes the broker reachable from places where a native MQTT client could not connect at all. The cost is some extra framing overhead and a more complex deployment, but the connectivity gain is often worth it.

For server-to-server or device-to-broker connections where neither side is a browser and the network does not enforce port restrictions, native MQTT remains the better default. It is lower overhead and simpler to deploy. MQTT over WebSockets is specifically for the cases where the WebSocket transport is needed, not as a general-purpose alternative.

Deployment topology: broker direct or reverse proxy in front

Two deployment shapes are common for MQTT over WebSockets in production, and choosing between them is a real architectural decision rather than an implementation detail.

Broker terminates WebSockets directly. The simpler shape: the broker itself listens on the WebSocket port (typically 443 with TLS) and handles the upgrade handshake, the subprotocol negotiation, and the WebSocket framing. Fewer moving parts, less latency in the path, and one component to monitor. The trade-off is that the broker is now exposed directly to the public internet on a well-known port and must handle TLS termination, certificate rotation, and any DDoS or rate-limiting concerns itself. For deployments where the broker has good operational tooling for those concerns, this is the cleanest setup.

Reverse proxy in front. The other shape places a reverse proxy (such as Nginx, HAProxy, Envoy, or a managed load balancer) in front of the broker. The proxy terminates TLS, performs the WebSocket upgrade, and forwards the established WebSocket to the broker over a private network, often as plain WebSocket on a non-public port. The broker only ever sees connections from the proxy. This shape gives you separation of concerns: TLS, certificate management, rate limiting, and public-facing security live on the proxy; brokering lives on the broker. It also makes scaling easier, because the proxy can load-balance across a cluster of brokers. The cost is one more component in the path, with its own configuration and potential timeout settings to align (covered in the pitfalls section).

The right choice depends on the deployment’s scale, the team’s familiarity with each component, and how strictly the broker has to be isolated from the network. There is no universally correct answer, but neither shape is unusual.

Common pitfalls with MQTT over WebSockets

A few mistakes recur often enough to be worth calling out.

Forgetting the subprotocol. A WebSocket connection without the right Sec-WebSocket-Protocol value will not be accepted as MQTT by a broker that requires the subprotocol. Modern client libraries handle this automatically, but if you are debugging at the network level, missing or wrong subprotocol values are a common cause of failed handshakes.

Mixed content errors. A page served over HTTPS cannot, in modern browsers, open a ws:// connection. The page must use wss://. Trying to use ws:// from a secure page results in the browser blocking the connection with a mixed-content error.

Path mismatches. The WebSocket URL includes a path, for example wss://broker.example.com/mqtt. Different brokers expect different paths; some accept any path, some require a specific one. A connection that fails immediately after the TLS handshake, without an MQTT-level error, is often a path mismatch at the WebSocket layer.

Proxies that drop idle connections. Some intermediate proxies and load balancers close idle WebSocket connections after a few minutes of silence, even if MQTT keep alive packets are still flowing under them, because they look at WebSocket-level framing rather than the contents. Setting MQTT keep alive shorter than any known proxy timeout, and configuring the proxy generously, are both worth checking when WebSocket connections die mysteriously.

Confusing native MQTT and WebSocket ports. A broker listening only on port 1883 for native MQTT will not accept WebSocket connections, and vice versa. The broker has to be configured to listen on the right port for each transport, and the client has to be pointed at the matching port and scheme.

Performance assumptions. WebSocket framing adds some per-packet overhead compared to raw TCP. For very high-volume or very latency-sensitive applications, native MQTT remains lighter. For browser use, that overhead is the price of being reachable from the browser at all and is rarely the bottleneck; for general deployments, choosing WebSockets when nothing requires them gives away a small amount of efficiency for no benefit.

How MQTT over WebSockets connects to the rest of MQTT

MQTT over WebSockets is a transport variant, not a separate protocol, so it interacts with every other MQTT feature:

  • The connection handshake still uses MQTT’s CONNECT and CONNACK packets, just carried inside WebSocket frames; everything in the connection-handshake article applies.
  • All MQTT features (QoS, sessions, retained messages, LWT, keep alive) work identically.
  • TLS security is provided by the underlying wss:// instead of by MQTT’s native mqtts:// over port 8883, but the protections are equivalent.
  • MQTT 5 features all apply unchanged, because the subprotocol negotiates only the message format and not which version of MQTT is used inside.

Used where it belongs (in browsers, and in port-restricted networks), MQTT over WebSockets extends MQTT’s reach without compromising any of its semantics.

Frequently asked questions

What is MQTT over WebSockets?

It is MQTT control packets carried inside WebSocket frames instead of directly over TCP. This lets web browsers, which cannot open raw TCP connections, act as full MQTT clients, and it lets MQTT traverse networks that block native MQTT ports.

Why can’t a browser speak native MQTT?

Browsers do not expose raw TCP sockets to JavaScript for security reasons. Native MQTT is a TCP-based protocol that needs a raw socket. WebSockets are the standard way for a browser to maintain a long-lived, bidirectional connection to a server, so MQTT is layered on top of them.

What is the difference between ws:// and wss://?

ws:// is unencrypted WebSocket, on port 80 by default. wss:// is TLS-encrypted WebSocket, on port 443 by default. For anything beyond local testing, use wss://. Browsers also block ws:// from pages served over HTTPS.

What ports does MQTT over WebSockets use?

By default, port 80 for ws:// and port 443 for wss://. These are the same ports as HTTP and HTTPS, which is part of why MQTT over WebSockets traverses restrictive firewalls and proxies that block native MQTT ports.

What is the Sec-WebSocket-Protocol header for in MQTT over WebSockets?

It negotiates the application-layer subprotocol carried inside the WebSocket frames. For MQTT, the value is mqtt (current) or mqttv3.1 (older, still seen for backward compatibility). The client lists the values it supports; the server picks one and echoes it back.

Do all MQTT features work over WebSockets?

Yes. QoS, persistent sessions, retained messages, Last Will and Testament, keep alive, and all MQTT 5 features behave identically over WebSockets, because the wrapping is purely a transport change.

Is MQTT over WebSockets slower than native MQTT?

Slightly, because of WebSocket framing overhead on every packet. For most applications the difference is negligible, especially compared to the connectivity benefits. For very high-throughput or strict latency requirements where the network allows native MQTT, native is the lighter choice.

Author: Zakaria El Intissar

I'm an automation and industrial computing engineer with 12 years of experience in power system automation, SCADA communication protocols, and electrical protection. I build tools and write guides for Modbus, DNP3, IEC 101/103/104, and IEC 61850 on ScadaProtocols.com to help engineers decode, analyze, and troubleshoot real industrial communication systems.