Configuration
The control API's environment, the relay's flags, scope file, systemd unit and sysctl, and the ingest gateway's flags and keys file.
Taken from crates/nearglass-control-api (src/bin/nearglass-control-api.rs,
deploy/), relay/ (the unit, the sysctl file, scope.example.json),
crates/nearglass-ingest/src/cli.rs and the moq-relay-ietf --help of the relay
package, on commit 79b96c7 of the SDK repository. The flag tables for the
relay and the gateway are what the built binaries print.
On the managed service you configure none of this: you hold keys and call the
control API. This page is for a deployment you run
yourself. Command line flags for nearglass itself are in the
CLI reference.
Every service runs as a systemd unit on a Linux host, with TLS ended in front
of the control API (by tailscale serve or your own front). There are no
containers.
The control API
nearglass-control-api serve reads its settings from the environment. Under
systemd that is an EnvironmentFile= named control-api.env under /etc, mode 600, owned
by root. Hashes and file paths only: no key is ever written there. A bad value
stops the service at start with a message that names the variable.
nearglass-control-api serve
nearglass-control-api keygen secret|ingest|publishable|operator|meterkeygen prints a new key once, and the SHA-256 line that the environment
file takes. The service never sees a secret or ingest key in plain text.
Not every deployment installs this binary: the managed service runs it for you.
Required
The service refuses to start without these.
| Variable | What |
|---|---|
NEARGLASS_API_STATE_FILE | the state file (streams, tenants, keys by hash). Under the unit: /var/lib/nearglass-control-api/state.json |
NEARGLASS_API_SECRET_KEY_SHA256 or NEARGLASS_API_OPERATOR_KEY_SHA256 | at least one: hashes of the default tenant's secret keys, or of the operator keys (for tenants). Comma separated SHA-256 hex digests, not keys |
NEARGLASS_API_ISSUER_KEY_FILE | with NEARGLASS_API_SECRET_KEY_SHA256: the default tenant's token signing key, P-256 PKCS#8 PEM |
NEARGLASS_API_RELAYS | comma separated relay URLs for players and publishers. Not needed when NEARGLASS_API_FLEET_FILE is set |
NEARGLASS_API_TENANT_KEY_DIR, NEARGLASS_API_SCOPE_FILE | with an operator key: where tenants' signing keys live (one file each, mode 0600), and the relay scope file the API writes |
Network and keys
| Variable | Default | What |
|---|---|---|
NEARGLASS_API_LISTEN | 127.0.0.1:8790 | address to listen on. Keep it on loopback and end TLS in front |
NEARGLASS_API_PUBLIC_URL | taken from NEARGLASS_API_HLS_URL when that is this API's own edge, else none | this API's public address (https://api.nearglass.com, no query). Seated answers name the seat's play_url under it. The player's token-url mode needs it |
NEARGLASS_API_TRUST_FORWARDED | off | 1: take the client address from X-Forwarded-For. Set it behind a proxy so the per-address rate limit counts viewers, not the proxy |
NEARGLASS_API_INGEST_KEY_SHA256 | none | hashes of the ingest gateway's keys (ik_...) |
NEARGLASS_API_PUBLISHABLE_KEYS | none | the publishable keys themselves (pk_...), comma separated: they are not secret. A key without the pk_ prefix is refused |
NEARGLASS_API_METER_KEY_SHA256 | none | hashes of the keys nearglass-meter posts records with |
NEARGLASS_API_OPERATOR_KEY_SHA256 | none | hashes of the operator keys that manage tenants |
Tokens and relays
| Variable | Default | What |
|---|---|---|
NEARGLASS_API_ISSUER | nearglass-token | the token issuer claim. Must match the relay's scope |
NEARGLASS_API_AUDIENCE | nearglass-relay | the audience claim. Must match the relay's scope |
NEARGLASS_API_VIEWER_TTL | 120 | viewer token lifetime, seconds. At most 3600 |
NEARGLASS_API_PUBLISHER_TTL | 3600 | publisher token lifetime, seconds. At most 7 days |
NEARGLASS_API_INGEST_TOKEN_TTL | 86400 | the gateway's publisher tokens, seconds. At most 7 days |
NEARGLASS_API_WATCH_GRACE | 600 | seconds. Must equal the relay scope's watch_grace_secs: the player's session renewal depends on it |
NEARGLASS_API_NAMESPACE_PREFIX | live | the first segment of a stream's default namespace, live/<id> |
NEARGLASS_API_RELAY_CERT_SHA256 | none | the relay certificate's SHA-256, hex. Only for a relay with a self-signed certificate |
NEARGLASS_API_RELAY_ADMIN_URL | none | the relay's admin port, to close the sessions of lost seats |
NEARGLASS_API_RELAY_ADMIN_KEY_FILE | none | the admin port's key file. The URL and the key file go together |
NEARGLASS_API_SCOPE_BASE | none | the operator's part of the relay scope file: the default scope and any others, with inline keys |
NEARGLASS_API_SCOPE_CLOCK_SKEW | 2 | seconds: clock_skew_secs in tenants' scopes. Small, so a controller's turn ends close to its end |
NEARGLASS_API_SCOPE_TTL | 10 | seconds: ttl_secs in tenants' scopes; how soon a changed limit reaches a scope in use |
Without NEARGLASS_API_RELAY_ADMIN_URL the API logs a warning: a lost seat's
open sessions then run until the relay's watch grace ends.
Addresses shown to customers
| Variable | What |
|---|---|
NEARGLASS_API_RTMP_URL | the gateway's RTMP address, shown when a stream is created (rtmp://ingest.nearglass.com:1935/live) |
NEARGLASS_API_SRT_URL | the gateway's SRT address (srt://ingest.nearglass.com:9000) |
NEARGLASS_API_HLS_URL | the LL-HLS playlist in every viewer descriptor. {namespace} and {id} are filled in. With the API's own edge: https://api.nearglass.com/v1/play/{id}/hls/index.m3u8 |
NEARGLASS_API_HLS_ORIGIN | the LL-HLS packager on this host (http://127.0.0.1:8888/hls/). Seats are then served and metered through /v1/play/{id}/hls/ |
Tenants and limits
| Variable | Default | What |
|---|---|---|
NEARGLASS_API_TENANT_VIEWER_CAP | 200 | a new tenant's seats over all its streams |
NEARGLASS_API_TENANT_MAX_STREAMS | 10 | a new tenant's streams |
NEARGLASS_API_TENANT_MAX_EGRESS_MBPS | set by the operator | a new tenant's send-rate ceiling |
NEARGLASS_API_DEFAULT_SPEND_CAP | set by the operator | a new tenant's monthly cap, USD. Not above the maximum |
NEARGLASS_API_MAX_SPEND_CAP | set by the operator | the most a tenant may set for itself, USD |
NEARGLASS_API_FREE_VIEWER_HOURS, NEARGLASS_API_FREE_GIB | set by the operator | a month's free allowance per tenant |
NEARGLASS_API_COST_PER_GIB, NEARGLASS_API_COST_PER_VIEWER_HOUR | set by the operator | what usage is priced at, USD |
A tenant's relay-scope max_sessions is twice its viewer cap plus 20.
Fleet and operator alerts
| Variable | What |
|---|---|
NEARGLASS_API_FLEET_FILE | regions, origins and edges: viewers get the healthy edges of every region, publishers the home origin. Replaces NEARGLASS_API_RELAYS. Described with regions in the Operate section |
NEARGLASS_API_OPERATOR_ALERT_URL_FILE | a file whose first line is the operator's alert webhook URL. The URL is a secret: a root-only file, loaded by systemd as a credential. An empty file means no channel |
NEARGLASS_API_OPERATOR_ALERT_FORMAT | discord (default), slack, or json signed with the secret file's first line |
NEARGLASS_API_OPERATOR_ALERT_SECRET_FILE | the signing secret for the json format |
NEARGLASS_API_STATUS_NAME | the product's name on the status page. Default nearglass |
NEARGLASS_API_OPERATOR_ALERT_URL (the URL itself, in the environment) is
refused at start, so a secret is never in a process environment.
The unit
crates/nearglass-control-api/deploy/nearglass-control-api.service runs the binary
from /opt/nearglass-control-api/ as a dynamic user with no extra privileges, a
read-only system, a private /tmp and no home. The signing key and the relay
admin key are root-owned files that systemd hands to the service with
LoadCredential, so the service reads them from $CREDENTIALS_DIRECTORY
(/run/credentials/nearglass-control-api.service/). It restarts on failure after
2 s.
systemctl daemon-reload && systemctl enable --now nearglass-control-apiA minimal environment file for one tenant:
NEARGLASS_API_LISTEN=127.0.0.1:8790
NEARGLASS_API_STATE_FILE=/var/lib/nearglass-control-api/state.json
NEARGLASS_API_ISSUER_KEY_FILE=/run/credentials/nearglass-control-api.service/issuer.key.pem
NEARGLASS_API_SECRET_KEY_SHA256=<sha256 from keygen secret>
NEARGLASS_API_INGEST_KEY_SHA256=<sha256 from keygen ingest>
NEARGLASS_API_PUBLISHABLE_KEYS=<pk_ from keygen publishable>
NEARGLASS_API_RELAYS=https://relay.nearglass.com:443/
NEARGLASS_API_RELAY_ADMIN_URL=http://127.0.0.1:4480
NEARGLASS_API_RELAY_ADMIN_KEY_FILE=/run/credentials/nearglass-control-api.service/relay-admin.key
NEARGLASS_API_RTMP_URL=rtmp://ingest.nearglass.com:1935/live
NEARGLASS_API_SRT_URL=srt://ingest.nearglass.com:9000
NEARGLASS_API_WATCH_GRACE=600
NEARGLASS_API_TRUST_FORWARDED=1The relay
The relay is moq-relay-ietf with Nearglass's patches, installed by the
nearglass-relay Debian package into /opt/nearglass-relay/moq-relay-ietf. The
package installs, and does not start, these files:
| File | What |
|---|---|
/opt/nearglass-relay/moq-relay-ietf | the relay |
/lib/systemd/system/nearglass-relay.service | the systemd unit (below) |
/etc/sysctl.d/90-nearglass-relay.conf | socket buffer limits (below) |
/etc/nearglass-relay/scope.example.json | an example scope file |
/usr/share/doc/nearglass-relay/ | the README and the copyright file |
Before it starts it needs /etc/nearglass-relay/cert.pem, key.pem and
scope.json, then systemctl enable --now nearglass-relay. It needs inbound UDP
on its port (QUIC and WebTransport). It reads only public keys: the signing
key stays with the control API.
Relay flags
From moq-relay-ietf --help. Flags marked "Nearglass" are patches this package
adds to the upstream relay.
| Flag | Default | What |
|---|---|---|
--bind <ADDR> | [::]:443 | listen address (UDP) |
--tls-cert <FILE> | none | PEM certificate chain. Repeat for several; the first match for the SNI is used, otherwise the last |
--tls-key <FILE> | none | PEM private key, one for every --tls-cert |
--tls-root <FILE> | system roots | trust this PEM root for connections to other relays |
--tls-disable-verify | off | skip certificate checks. Development and between relays only. Also TLS_DISABLE_VERIFY |
--scope-config <FILE> | none | JSON file of per-scope token policies. Turns tokens on. Also MOQ_RELAY_SCOPE_CONFIG. Re-read for new sessions; see the scope file |
--max-request-id <N> | 100 | the largest request id plus one advertised at setup. The default runs out after about 50 viewer sessions in one-to-one mode; the unit sets 1000000 |
--cache-idle-timeout <SECONDS> | 30 | how long to keep a cached track with no subscribers. 0 never evicts. Use 30 for broadcast and 1 for one-to-one streams |
--coordinator-file <FILE> | /tmp/moq-coordinator.json | namespace registrations shared by relays on one host. The unit puts it under /run/nearglass-relay/ so a restart starts clean |
--metrics-addr <ADDR> | off | serve Prometheus metrics at http://<addr>/metrics |
--session-log <FILE> | off | Nearglass: append one JSON line per closed token session. This is the metering source. Created if missing |
--admin-addr <ADDR> | off | Nearglass: serve the admin port here. Loopback only. Needs --admin-key-file |
--admin-key-file <FILE> | none | Nearglass: the admin port's bearer key, the first line of the file, at least 32 characters. Readable by the relay's user only |
--internal-peer <IP> | none | Nearglass: treat this source address as a peer relay. Repeat. Internal sessions skip token checks, so list only addresses your network authenticates, such as a WireGuard tunnel address |
--internal-peer-allow-public | off | Nearglass: allow a public --internal-peer address |
--cc <bbr|cubic> | bbr | Nearglass: congestion controller for every connection |
--ingest-lane <true|false> | true | Nearglass: run the UDP socket and publishing sessions on their own thread, so an overloaded relay degrades viewers and not ingest |
--viewer-send-window-kb <KIB> | 1024 | Nearglass: the unsent or unacknowledged bytes held for each public session. 0 is the QUIC library's own limit |
--admission <on|observe|off> | on | Nearglass: a saturated relay refuses new viewers and keeps the ones it has. observe measures and refuses nothing. Also NEARGLASS_ADMISSION |
--admission-busy <0 to 1> | 0.85 | Nearglass: the busy share at which the relay counts as saturated; it admits again 0.15 below. Also NEARGLASS_ADMISSION_BUSY |
--admission-retry-ms <MS> | 2000 | Nearglass: the retry interval a refusal carries |
--setup-rate-per-ip <RATE/BURST> | 200/1000 | Nearglass: new connections a second from one address, and the burst. Over it a connection is refused before its TLS handshake. 0 is off. Also NEARGLASS_SETUP_RATE_PER_IP |
--setup-rate-global <RATE/BURST> | 1000/2000 | Nearglass: the same for the whole relay. Also NEARGLASS_SETUP_RATE_GLOBAL |
--announce <URL> | none | forward publish announcements to a server for authorization and routing. Without it the relay accepts every unique namespace |
--api <URL>, --node <HOST>, --api-url <URL>, --api-ttl <SECONDS> | none, none, none, 600 | upstream's cluster coordination through a moq-api server, in place of the coordinator file |
--dev, --qlog-dir, --mlog-dir, --qlog-serve, --mlog-serve | off | development: serve the certificate fingerprint, and write per-connection logs |
The relay ignores SIGINT: stop it with SIGTERM.
Environment variables the relay also reads:
| Variable | Default | What |
|---|---|---|
NEARGLASS_UDP_SNDBUF_KB, NEARGLASS_UDP_RCVBUF_KB | derived | UDP socket buffer sizes in KiB. By default the send buffer is sized from the default route's link speed (512 KiB when it is unknown) and the receive buffer is 8 MiB. 0 keeps the kernel default |
NEARGLASS_ZEROCOPY | off | 1: forward each upstream payload to a subscriber by reference instead of copying it |
NEARGLASS_GROUP_BOUND | on | off: do not bound the groups in flight per subscription |
The relay unit
/lib/systemd/system/nearglass-relay.service as the package installs it. Copy it
to /etc/systemd/system/ to adjust the paths and the bind address.
[Service]
ExecStart=/opt/nearglass-relay/moq-relay-ietf \
--bind 0.0.0.0:443 \
--tls-cert /etc/nearglass-relay/cert.pem --tls-key /etc/nearglass-relay/key.pem \
--scope-config /etc/nearglass-relay/scope.json \
--coordinator-file /run/nearglass-relay/coordinator.json \
--max-request-id 1000000 \
--cache-idle-timeout 30 \
--metrics-addr 127.0.0.1:9090
KillSignal=SIGTERM
Restart=on-failure
RestartSec=2
RuntimeDirectory=nearglass-relay
DynamicUser=yes
AmbientCapabilities=CAP_NET_BIND_SERVICE
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
LimitNOFILE=65536The unit does not set --admin-addr, --admin-key-file or --session-log.
The control API needs the admin port to close lost seats' sessions, and
metering needs the session log, so add them for a deployment with seat
limits:
--admin-addr 127.0.0.1:4480 --admin-key-file /etc/nearglass-relay/admin.key \
--session-log /var/lib/nearglass-relay/sessions.jsonlThe relay runs as a dynamic user, so give it a state directory
(StateDirectory=nearglass-relay) for the log, and let it read the key file
through LoadCredential.
sysctl
/etc/sysctl.d/90-nearglass-relay.conf (applied on the next boot, or with
sysctl --system):
net.core.wmem_max = 8388608
net.core.rmem_max = 8388608The relay asks for large UDP buffers. Without CAP_NET_ADMIN the kernel caps
each request at these two limits, 212992 bytes on a stock Debian or Ubuntu
kernel. The relay logs a warning at start when a request was capped, and a
UDP socket bound line with the requested and granted sizes.
The scope file
--scope-config names a JSON file of token policies, one per scope. A scope
is the URL path a client connects on: https://relay.nearglass.com/tn_abc
is the scope /tn_abc. The default entry covers every scope without its
own. Scopes in the file require a valid token; the file is re-read for new
sessions, so an edit applies once a scope's cached policy expires (ttl_secs).
If the file later goes missing or invalid, a scope keeps its cached policy
until it expires, then refuses new sessions rather than admitting without a
token.
{
"default": {
"auth": {
"keys": [{ "kid": "nearglass-2026-10", "alg": "ES256", "pem_file": "issuer.pub.pem" }],
"issuers": ["nearglass-token"],
"audiences": ["nearglass-relay"],
"clock_skew_secs": 60,
"ttl_secs": 300
}
},
"scopes": {
"/tn_abc": { "auth": { "...": "same fields" } }
}
}Field in auth | What |
|---|---|
keys | issuer public keys: kid, alg (ES256), and the key as pem (inline) or pem_file (a path, relative to the scope file) |
issuers, audiences | accepted claims. Match NEARGLASS_API_ISSUER and NEARGLASS_API_AUDIENCE, or nearglass token mint --issuer and --audience |
clock_skew_secs | allowed clock difference. A publisher session is closed at exp plus this, so keep it small where controller tokens are used |
ttl_secs | how long a scope's policy is cached |
max_sessions | live token sessions in the scope, every role. The next one is closed with "scope full". Omit for no limit; 0 is refused |
max_publishers | publishing sessions in the scope |
max_egress_mbps | above this send rate the relay admits no new viewer in the scope. Live ones keep watching |
require_token_id | refuse tokens that have no cti. Default off |
watch_grace_secs | reads (viewers) keep working this long after exp plus skew. At most 3600. 0 (default) is upstream's behaviour. Publishing never outlives exp |
When the control API writes this file (NEARGLASS_API_SCOPE_FILE), it takes the
operator's NEARGLASS_API_SCOPE_BASE (the default scope and others, with inline
pem keys only: a pem_file path would be relative to the wrong directory)
and adds one scope per tenant, /tn_<id>, with the tenant's key and limits. A
base must not name a /tn_ scope. With no base, the default scope refuses
every token, so a path that is not a tenant's is closed.
The admin port
With --admin-addr and --admin-key-file, loopback only. Every request
carries the key as Authorization: Bearer <key>.
| Request | What |
|---|---|
POST /close?token_id=<id>[&exp=<unix s>] | close the sessions with this token id and refuse the id until its token expires |
GET /sessions | session counts per scope |
GET /usage | the relay's usage and its admission state, for the node agent |
A wrong or missing key is 401.
The session log
One JSON line per closed token session, with these fields: scope,
token_id, sub, role, start_ms, end_ms, bytes_out, token_exp
and close (closed, error, ended, admin or expired). Refused
sessions leave no record. nearglass-meter ships it to the control API.
Relay metrics
With --metrics-addr, at /metrics. Names from the source:
moq_relay_seat_refused_total{reason} (scope_full, token_id_in_use,
token_closed), moq_relay_seat_sessions,
moq_relay_seat_admin_closed_total, moq_relay_seat_expired_total,
moq_relay_seat_lapsed_total, moq_relay_admin_denied_total,
moq_relay_setup_refused_total and moq_relay_clock_synchronized.
The ingest gateway
nearglass ingest (flags in the CLI reference) takes
encoders over RTMP, RTMPS, SRT and WHIP. On a self-hosted deployment run it
as a systemd unit beside the relay, with its keys from one of two places:
| Source | Flags | Keys come from |
|---|---|---|
| the control API | --control-api, --control-api-key-file | the API: stream keys and each stream's publisher token, synced every 3 s. A stream deleted in the API stops being admitted |
| a keys file | --keys, --token-file | a file you keep, with one publisher token for the streams' namespaces |
Give exactly one. With --control-api the gateway starts with the API's
list, so the first encoder is not refused for want of a sync.
Ports are whatever you pass: the API only shows customers the addresses in
NEARGLASS_API_RTMP_URL and NEARGLASS_API_SRT_URL, so keep those equal to the
gateway's --rtmp and --srt. RTMPS and WHIP need a certificate
(--rtmps-cert, --rtmps-key, or --whip-cert, --whip-key).
The ingest keys file
nearglass ingest keygen prints the key and one entry. A keys file is JSON with
a streams list:
{ "streams": [
{ "name": "studio-a", "key_sha256": "<64 hex>", "namespace": "warp/ingest/studio-a",
"protocols": ["rtmp", "rtmps", "srt", "whip"] }
] }| Field | Required | What |
|---|---|---|
name | yes | the stream's name, for logs and status. Not secret |
key_sha256 | yes | SHA-256 of the key, lowercase hex. Only the hash is stored |
namespace | yes | the broadcast namespace, / separated |
protocols | yes | any of rtmp, rtmps, srt, whip, framed |
max_kbps | no | bitrate cap in kbit/s. 0 or absent: none. Read but not enforced yet: the gateway accepts a higher bitrate |
expires | no | expiry as Unix seconds. Absent: never |
scope | no | the relay scope (URL path) to publish on, such as /tn_abc. Absent: the relay URLs as given |
One publisher holds a key at a time. A second may take over after the first has been silent for 5 s, which is an encoder reconnecting after a drop.
Environment
The gateway reads RUST_LOG and TLS_DISABLE_VERIFY only. Everything else
is a flag.