---
url: https://doc.moq.dev/draft/qmux-websocket.md
description: >-
  QMux draft-ietf-quic-qmux is a polyfill that runs QUIC applications over an
  ordered, reliable byte-stream transport such as TCP with TLS.
---

# QMux over WebSocket

::: info
Rendered from the Internet-Draft source in this repository.
Submitted versions are on the [IETF datatracker](https://datatracker.ietf.org/doc/draft-lcurley-qmux-websocket/).
:::

## Abstract

QMux [draft-ietf-quic-qmux](https://datatracker.ietf.org/doc/draft-ietf-quic-qmux/) is a polyfill that runs QUIC applications over an ordered, reliable byte-stream transport such as TCP with TLS.
This document defines a binding for QMux over WebSocket [RFC 6455](https://www.rfc-editor.org/rfc/rfc6455).
A WebSocket binding lets QUIC applications reach environments where UDP is blocked and where only an HTTP/WebSocket stack is available, including web browsers that lack WebTransport.

## Note to Readers

This document was generated by an AI model from the implementation at [github.com/moq-dev/moq](https://github.com/moq-dev/moq) and is maintained alongside it.
Submit an [issue](https://github.com/moq-dev/moq/issues) or [PR](https://github.com/moq-dev/moq/pulls) if this spec sucks and you want to fix anything.

## Conventions and Definitions

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119](https://www.rfc-editor.org/rfc/rfc2119) [RFC8174](https://www.rfc-editor.org/rfc/rfc8174) when, and only when, they appear in all capitals, as shown here.

This document uses the terms QMux Record, QMux Frame, and transport parameter as defined in [draft-ietf-quic-qmux](https://datatracker.ietf.org/doc/draft-ietf-quic-qmux/), and the terms WebSocket connection, message, frame, and subprotocol as defined in [RFC 6455](https://www.rfc-editor.org/rfc/rfc6455).

## Introduction

QMux [draft-ietf-quic-qmux](https://datatracker.ietf.org/doc/draft-ietf-quic-qmux/) lets an application written against the QUIC stream and datagram API run over an ordered, reliable byte-stream transport.
It defines a binding over TCP and over TLS, but a WebSocket binding is out of scope for the QUIC working group charter.

A WebSocket binding is nevertheless useful: WebSocket [RFC 6455](https://www.rfc-editor.org/rfc/rfc6455) is available where UDP is blocked, in web browsers that lack WebTransport, and behind HTTP load balancers that cannot route raw TCP or QUIC.

This document specifies how to carry QMux over WebSocket: the message framing, the subprotocol negotiation used in place of TLS ALPN, keep-alive behavior, connection close, and datagrams.
All other QMux semantics (in-order STREAM frame delivery, stream identifiers, flow control, and transport parameters) apply unchanged from [draft-ietf-quic-qmux](https://datatracker.ietf.org/doc/draft-ietf-quic-qmux/).

This binding is application agnostic: any QUIC application that can run over QMux can run over QMux over WebSocket.
Media over QUIC Transport [draft-ietf-moq-transport](https://datatracker.ietf.org/doc/draft-ietf-moq-transport/) is the motivating use case, but nothing in this document is specific to it.

## WebSocket Binding Overview

A QMux-over-WebSocket connection is an ordinary WebSocket connection [RFC 6455](https://www.rfc-editor.org/rfc/rfc6455) whose binary messages carry QMux frames, taking the place of the underlying byte-stream transport in [draft-ietf-quic-qmux](https://datatracker.ietf.org/doc/draft-ietf-quic-qmux/).

Both the QMux Record layer and the WebSocket message layer provide self-delimiting messages over a reliable, ordered byte stream.
The two layers are therefore collapsed: instead of prefixing each Record with its `Size`, the binding relies on the WebSocket message boundary to delimit it.

## Establishing a Connection

A client establishes a QMux-over-WebSocket connection by opening a WebSocket connection per [RFC 6455](https://www.rfc-editor.org/rfc/rfc6455) (the opening handshake over HTTP/1.1) or per [RFC 9220](https://www.rfc-editor.org/rfc/rfc9220) (the bootstrapping mechanism over HTTP/2 or HTTP/3).

The `ws` URI scheme is used over an unencrypted transport and the `wss` URI scheme is used over a TLS-encrypted transport.
Deployments SHOULD use `wss`; an application that expects a TLS transport when running natively over QUIC SHOULD require `wss` here.

How the underlying connection is authenticated and authorized is out of scope for this document, as it is for [draft-ietf-quic-qmux](https://datatracker.ietf.org/doc/draft-ietf-quic-qmux/).

## Subprotocol Negotiation

QMux over TCP/TLS uses TLS ALPN [RFC 8446](https://www.rfc-editor.org/rfc/rfc8446) to agree on the application protocol.
WebSocket has no ALPN exchange, so this binding uses the WebSocket subprotocol negotiation of [RFC 6455](https://www.rfc-editor.org/rfc/rfc6455) Section 1.9 (the `Sec-WebSocket-Protocol` header) in its place, carrying the same application protocol identifier.

The subprotocol identifier is exactly the identifier the application would use as its ALPN over native QUIC, for example `moq-transport-18`.
It also determines the QMux wire-format version, so there is no separate QMux version negotiation (see [QMux Version](#versions)).

A client offers one or more identifiers in the `Sec-WebSocket-Protocol` request header, in decreasing order of preference, one per protocol version it is willing to use.
A server selects at most one, in its own order of preference, and echoes it in the response header; if it supports none of the offered identifiers, it MUST fail the handshake.
A client MUST treat a response value it did not offer as a failed handshake per [RFC 6455](https://www.rfc-editor.org/rfc/rfc6455), and this binding additionally requires a selection: a client MUST treat the absence of a `Sec-WebSocket-Protocol` response header as a failed handshake too, since without one no QMux version was agreed.

## Record Framing {#framing}

Each WebSocket binary message carries exactly one QMux Record's `Frames` field: one or more QMux frames concatenated, as defined in [draft-ietf-quic-qmux](https://datatracker.ietf.org/doc/draft-ietf-quic-qmux/).
Because the WebSocket framing layer already delimits each message, the QMux Record `Size` field is redundant: it MUST NOT be transmitted and MUST NOT be expected by the receiver.

A QMux-over-WebSocket record is therefore:

```
WebSocket Binary Message {
  Frames (..),
}
```

The frames inside a message are encoded exactly as in [draft-ietf-quic-qmux](https://datatracker.ietf.org/doc/draft-ietf-quic-qmux/), including the in-order STREAM frame requirement: for each QUIC stream, a sender MUST send that stream's payload in order, so a receiver can deliver payload to the application as it arrives without reassembly.

An endpoint MAY place multiple frames in a single binary message and MAY split a logical sequence of frames across multiple messages, subject to the constraint that each STREAM frame's payload bytes are delivered in order.
An empty binary message (zero frames) is permitted and carries no frames; a receiver MUST accept it and treat it as a no-op.

The maximum size of a binary message is bounded by the `max_record_size` transport parameter defined in [draft-ietf-quic-qmux](https://datatracker.ietf.org/doc/draft-ietf-quic-qmux/) and by any WebSocket implementation limits.
An endpoint MUST NOT send a binary message whose payload exceeds the peer's advertised `max_record_size`, and MAY treat receipt of an oversized message as a connection error.

## WebSocket Message Types

This binding uses WebSocket message and control frames as follows:

* *Binary messages* carry QMux frames as defined in [Record Framing](#framing).
* *Text messages* MUST NOT be sent. A receiver MUST treat a text message as a connection error and close the WebSocket connection.
* *Close frames* terminate the connection as defined in [Connection Close](#close).
* *Ping and Pong frames* are used for keep-alive as defined in [Keep-Alive and Idle Timeout](#keepalive) and are otherwise handled by the WebSocket layer; they carry no QMux frames.

The first QMux frame sent by each endpoint MUST be the `QX_TRANSPORT_PARAMETERS` frame, exactly as required by [draft-ietf-quic-qmux](https://datatracker.ietf.org/doc/draft-ietf-quic-qmux/); this binding does not change that requirement.

## QMux Version {#versions}

The QMux version is not signaled on the wire.
As with QMux over TLS, it is implied by the negotiated application protocol: each application protocol that runs over QMux specifies which QMux version each of its identifiers uses.
An application protocol used with this binding MUST select a QMux version that provides the Record layer (see [Record Framing](#framing)), i.e. [draft-ietf-quic-qmux](https://datatracker.ietf.org/doc/draft-ietf-quic-qmux/) or later.

## Keep-Alive and Idle Timeout {#keepalive}

A WebSocket connection has no built-in idle timeout: if the peer's host crashes without a TCP FIN, the local socket can remain "open" for hours.

To detect a dead peer, an endpoint SHOULD send WebSocket Ping frames [RFC 6455](https://www.rfc-editor.org/rfc/rfc6455) periodically and SHOULD close the connection if no WebSocket frame of any kind is received within a timeout, a small multiple of the ping interval.
Reasonable defaults are a 5-second ping interval and a 30-second timeout, but the values are a local policy decision.
Receipt of any WebSocket frame from the peer (binary, Ping, or Pong) resets the idle timer.

This keep-alive operates at the WebSocket layer and is separate from the QMux `max_idle_timeout` transport parameter and `QX_PING` frame, either of which an endpoint MAY also use.

## Datagrams {#datagrams}

QMux datagrams are supported.
They are negotiated and encoded exactly as in [draft-ietf-quic-qmux](https://datatracker.ietf.org/doc/draft-ietf-quic-qmux/): an endpoint advertises the datagram transport parameter and carries QMux DATAGRAM frames inside binary messages, like any other frame ([Record Framing](#framing)).

## Connection Close {#close}

An endpoint terminates a QMux-over-WebSocket connection by sending a WebSocket Close frame [RFC 6455](https://www.rfc-editor.org/rfc/rfc6455) and then closing the underlying transport.

A QMux `CONNECTION_CLOSE` frame, if sent, conveys the QMux-level error code and reason and SHOULD be sent in a final binary message before the WebSocket Close frame.
Because the WebSocket layer provides its own connection close, there is no draining period: an endpoint MAY close immediately after sending its Close frame.

Receipt of a WebSocket Close frame, or loss of the underlying transport, terminates the QMux connection and all of its streams.

## Security Considerations

This binding inherits the security considerations of QMux [draft-ietf-quic-qmux](https://datatracker.ietf.org/doc/draft-ietf-quic-qmux/), WebSocket [RFC 6455](https://www.rfc-editor.org/rfc/rfc6455), and, when `wss` is used, TLS [RFC 8446](https://www.rfc-editor.org/rfc/rfc8446).

Carrying QMux over WebSocket does not add or remove any QMux-level security property.
In particular, this binding provides no transport-layer confidentiality or integrity of its own; deployments that require those properties MUST use `wss` (WebSocket over TLS).

The keep-alive mechanism in [Keep-Alive and Idle Timeout](#keepalive) causes an endpoint to send periodic Ping frames.
An endpoint SHOULD bound the rate at which it sends and responds to Ping/Pong frames to avoid amplification or resource-exhaustion concerns.

Because a server selects the subprotocol from a client-supplied list, a server MUST validate the selected identifier against its own supported set and MUST NOT echo an arbitrary client-supplied value, as required by [RFC 6455](https://www.rfc-editor.org/rfc/rfc6455).

## IANA Considerations

This document has no IANA actions.

This binding defines no subprotocol identifiers of its own: the `Sec-WebSocket-Protocol` value is the application protocol identifier, which is owned by the application protocol's specification (for example, [draft-ietf-moq-transport](https://datatracker.ietf.org/doc/draft-ietf-moq-transport/)).
Whether those identifiers are registered in the WebSocket Subprotocol Name Registry [RFC 6455](https://www.rfc-editor.org/rfc/rfc6455) is therefore up to each application protocol, not this document.

## Normative References

* [draft-ietf-quic-qmux](https://datatracker.ietf.org/doc/draft-ietf-quic-qmux/)
* [RFC 6455](https://www.rfc-editor.org/rfc/rfc6455)

## Informative References

* [RFC 8446](https://www.rfc-editor.org/rfc/rfc8446)
* [RFC 9220](https://www.rfc-editor.org/rfc/rfc9220)
* [draft-ietf-moq-transport](https://datatracker.ietf.org/doc/draft-ietf-moq-transport/)

## Acknowledgments

QMux is the work of the QUIC working group; this document only defines a WebSocket binding for it.
Thanks to the Media over QUIC working group for motivating a transport that works where UDP does not.
