MoQ is under active development. APIs will change, but we keep backwards wire compatibility.

Skip to content

moq-relay ​

moq-relay routes broadcasts from publishers to subscribers. It caches groups, merges duplicate subscriptions, and never parses the media, so one relay serves video, audio, and data alike.

Features ​

  • QUIC, WebTransport, and WebSocket listeners, so browsers and native clients connect to one process.
  • Path-scoped authentication with JWTs, mTLS for peers, and anonymous patterns, decided by an auth server or a static grant. See Authentication.
  • Clustering across hosts and regions with hop-list routing, per-link costs, LAN discovery, and dynamic peer lists. See Clustering.
  • A group cache with byte and age budgets, so late joiners and the HLS gateway can fetch recent history.
  • HTTP endpoints to list broadcasts, fetch groups, probe health, and scrape Prometheus metrics. See HTTP.
  • Live stats published as MoQ tracks per node and per tenant, split by billing tier.
  • Plaintext TCP and Unix-socket listeners for trusted local workers, and experimental iroh peer-to-peer.
  • Hot reload of certificates and trust roots.

Run ​

bash
cargo install moq-relay          # or brew, apt, dnf, winget, docker; see Install
moq-relay relay.toml

The .deb and .rpm systemd service reads /etc/moq-relay/relay.toml using the same positional config argument.

The relay takes one TOML file. A local development config:

toml
[listen]
bind = "[::]:4443"
tls.generate = ["localhost"]

[web.http]
listen = "[::]:4443"   # serves the certificate fingerprint for local browsers

[auth]
public = "**"          # anonymous access to everything; development only

Every option is also a --flag or MOQ_* environment variable, and RUST_LOG controls logging. The configuration reference covers every section, and demo/relay/ has working configs for development, production, and a cluster.

Embed ​

moq-relay is also a library. An application that wants extra HTTP routes or in-process workers against the cluster origin loads a Relay and calls run. The owner keeps the listeners, QUIC workers, and shutdown joins, so a new socket added in a library update cannot be dropped by a .. pattern that still compiles.

rust
use axum::routing::get;
use moq_relay::{Config, Relay};

let relay = Relay::load(config).await?;
let origin = relay.cluster().origin.clone();
let trigger = relay.shutdown_trigger().clone();
let web = relay.web().routes().route("/hello", get(|| async { "hello" }));
relay.with_web(web).run().await?;

Relay::load binds every socket, so a taken port fails there. Read the actual addresses with quic_addr(), tcp_addr(), web_addrs(), and internal().addr(), including ports assigned for :0. Clone ready() before spawning run, then await ready.wait() when startup must finish before other workers begin. config() returns the resolved settings; cluster().id() returns the chosen origin ID. with_listeners() registers an extra TCP listener's accept health at the relay's /metrics. The test-support feature provides test_relay() with ephemeral ports, generated TLS, and a certificate fingerprint for client pinning.

The accessors borrow and run consumes the relay, so clone cluster, auth, client, stats, shutdown, and shutdown_trigger for application tasks before calling it. trigger.start() drains every session with a GOAWAY, including any that connect afterwards, and run returns once every session has left or the drain window elapses, with the listeners released and the workers joined. run also starts the drain on SIGTERM or SIGINT. An application that owns those signals, for example to withdraw the node from DNS and wait out the TTL before draining, calls with_signals(false) and fires the trigger itself. Build routes from web().routes() (or internal().routes()): with_web replaces the router, so Router::new() drops the built-in routes. Extra listeners (RTMP, SRT, ...) sit beside run in the application's select!. The drain reaches only MoQ sessions: an extra listener has no GOAWAY, so the application stops it on its own deadline and leaves the encoder to reconnect. runtime.workers and runtime.io_uring stay inside the owner; do not split the worker group yourself. An application that decides admissions itself leaves [auth] empty and answers relay.admissions(); see Authentication. See rs/moq-relay/examples/embed.rs.

Operate ​

TaskGuide
Expose it publicly with TLS and host tuningProduction deployment
Decide who may publish and subscribe whereAuthentication
Add more relaysClustering
Monitor, debug, fetch historyHTTP endpoints

Troubleshooting ​

  • Address already in use: something else holds the UDP or TCP port.
  • Certificate errors: the hostname must match the certificate. Local browsers need the fingerprint served over [web.http].
  • Connection timeout: UDP isn't reaching the relay, or the client URL names the wrong port.
  • Unauthorized / forbidden: the token's paths don't cover the connection path, or the broadcast a session asked for. See path matching.

Licensed under MIT or Apache-2.0