Appearance
Ports
The Host itself listens on zero ports. NATS and the gateway runtime own ports. The Host's HTTP gateway is a runtime, not part of the Host process. Per-runtime served apps allocate their own ports.
This page documents the defaults; configure them in host.json (NATS, gateway HTTP) or via matrix up --port (per-runtime served apps).
Defaults at a glance
Wrapper install (per-user)
| Service | Port | Source |
|---|---|---|
| NATS TCP | 4270 | defaultHostConfig (host-paths.ts:30) |
| NATS WebSocket | 4271 | defaultHostConfig (host-paths.ts:31) |
| Gateway HTTP | OS-allocated unless --http-port <p> | _resolveHttpStatusAddress (matrix-host-service.ts:955-964) |
| Per-runtime served app | OS-allocated unless --port <p> | allocateLocalPort (cli.ts:837-854) |
.deb install
| Service | Port | Source |
|---|---|---|
| NATS TCP | 4222 | build-deb-installer.js:336 |
| NATS WebSocket | 4223 | build-deb-installer.js:341 |
| Gateway HTTP | 3100 | build-deb-installer.js:310 |
| Per-runtime served app | OS-allocated unless --port <p> | same as wrapper |
The .deb defaults are pinned because production runs behind a nginx or Caddy reverse proxy that expects 3100, and the older NATS port 4222 is the documented default.
NATS
transport.nats.port and transport.nats.wsPort in host.json.
json
{
"transport": {
"nats": {
"mode": "embedded",
"port": 4270,
"wsPort": 4271,
"url": "nats://127.0.0.1:4270",
"wsUrl": "ws://127.0.0.1:4271"
}
}
}When mode is embedded, the Host writes a nats-server.conf that binds these ports. When mode is external, the Host connects to the url you provide and ignores port/wsPort.
The wrapper's NATS sibling supervision uses these same fields via natsConfigPaths (hivecast.mjs:584-604).
Browsers connect to the WebSocket port
Per repo CLAUDE.md Rule 7, browsers always reach NATS via a same-origin WebSocket at /nats-ws, not directly to the WS port. The gateway runtime proxies /nats-ws to transport.nats.wsUrl. Do not expose the NATS WS port directly to a browser; that breaks the same-origin contract.
Gateway HTTP
http.port in host.json controls the gateway runtime's bind port:
json
{
"http": {
"host": "127.0.0.1",
"port": 3100
}
}port: null— let the OS pick. Wrapper default. The allocated port appears inhost.status.jsonhttp.port.port: <number>— pin. Required for stable URLs.host: "0.0.0.0"— bind on all interfaces (containers and shared workstations). The.debpostinst auto-detects containers and sets this to0.0.0.0; otherwise127.0.0.1.
Override at install:
bash
hivecast install --http-port 3100
hivecast install --http-port 0 # OS-allocated
hivecast install --http-port auto # same as 0Per-runtime served-app ports
matrix up --serve --port <p> pins a port; without --port, the Host calls allocateLocalPort (cli.ts:837-854):
net.createServer().listen(0, '127.0.0.1', ...).- Read
address().port. - Close the server.
- Pass the port to the runtime via the
--env-file http.portfield.
This is racy — between "close" and "spawn the runtime," another process could grab the port. In practice the window is small enough that it does not matter.
Mount-conflict checks
The Host refuses to start two runtimes on the same routePrefix (see _findRuntimeStartConflict, matrix-host-service.ts:543-576):
Runtime CHAT cannot start: web route "/apps/chat/" is already owned by RUNTIME-HOST-LOCAL-ABC-CHATThis protects against matrix up @open-matrix/chat against a Host that already has chat running. To pin a different served port for a second copy, the operator must also pick a different routePrefix — typically by editing the package's matrix.json webapp.appName, which is uncommon.
What's listening, in practice
bash
# Linux
sudo ss -tlnp | grep -E '(4222|4223|4270|4271|3100)'
# Or per-Host:
hivecast status --home <home> | jq '{ httpPort: .http.port, natsUrl: .transport.nats.url, natsWsUrl: .transport.nats.wsUrl }'
matrix runtimes --home <home> \
| jq '.runtimes[] | { id: .runtimeId, port: .metadata.webapp.port, route: .metadata.webapp.routePrefix }'