Skip to content

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)

ServicePortSource
NATS TCP4270defaultHostConfig (host-paths.ts:30)
NATS WebSocket4271defaultHostConfig (host-paths.ts:31)
Gateway HTTPOS-allocated unless --http-port <p>_resolveHttpStatusAddress (matrix-host-service.ts:955-964)
Per-runtime served appOS-allocated unless --port <p>allocateLocalPort (cli.ts:837-854)

.deb install

ServicePortSource
NATS TCP4222build-deb-installer.js:336
NATS WebSocket4223build-deb-installer.js:341
Gateway HTTP3100build-deb-installer.js:310
Per-runtime served appOS-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 in host.status.json http.port.
  • port: <number> — pin. Required for stable URLs.
  • host: "0.0.0.0" — bind on all interfaces (containers and shared workstations). The .deb postinst auto-detects containers and sets this to 0.0.0.0; otherwise 127.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 0

Per-runtime served-app ports

matrix up --serve --port <p> pins a port; without --port, the Host calls allocateLocalPort (cli.ts:837-854):

  1. net.createServer().listen(0, '127.0.0.1', ...).
  2. Read address().port.
  3. Close the server.
  4. Pass the port to the runtime via the --env-file http.port field.

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-CHAT

This 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 }'

See also