Skip to main content
Docs navigation

Network requirements

The domains, ports, and protocols runner hosts and workstations need to reach.

What has to be reachable#

Use these rows to build outbound rules for runner hosts and workstations. "Your emisar origin" is the control plane you connect to — on the hosted control plane, https://emisar.dev.

Operation and component Direction Destination and port Protocol Why When required
Runner registration Outbound Your emisar origin, TCP 443 HTTPS POST /runner/register Exchanges the enrollment key for this runner's own token First connect, and again after an enrollment-key change
Runner token refresh Outbound Your emisar origin, TCP 443 HTTPS POST /runner/token/refresh Swaps the runner's token for a successor before the old one expires On the next connect after its token is 60 days old
Runner control connection Outbound Your emisar origin, TCP 443 WSS /runner/socket/websocket Carries every dispatch, result, and heartbeat on one connection Continuously, for as long as the runner runs
Installer release download (runner and bridge) Outbound Your emisar origin, TCP 443 HTTPS Downloads the release manifest, tarball, checksums, and signed checksum bundle from /releases Only while install.sh or install-mcp.sh runs
Release checksum trust roots Outbound tuf-repo-cdn.sigstore.dev and tuf-repo.github.com, TCP 443 HTTPS Lets GitHub CLI authenticate the downloaded checksum bundle against public trust roots During install and upgrade when the verifier cache is empty or refreshes
GitHub release mirror fallback Outbound api.github.com, github.com, and release-assets.githubusercontent.com, TCP 443 HTTPS Provides the optional secondary release source Only when the Emisar release path is unavailable or EMISAR_REPO points at another repository
Pack install and update Outbound Your emisar origin and registry.emisar.dev, TCP 443, or your configured artifact hosts HTTPS Fetches the immutable pack tarball named by the registry index Only while emisar pack install or pack update runs
Action and provider traffic Outbound Whatever that pack's command talks to, on the provider's own port The provider's own protocol The action does its work. This traffic never passes through emisar. While that action runs
MCP bridge Outbound Your emisar origin, TCP 443 HTTPS POST /api/mcp/rpc One request per JSON-RPC frame from the LLM client While an LLM client session is active
LLM client to bridge Local, no network The bridge process on the same machine stdio pipes The client starts the bridge as a child process Whenever the client is running
Name resolution Outbound Your configured resolver DNS Resolves the emisar, registry, provider, and optional GitHub fallback hosts On connect, reconnect, install, and update
Clock synchronization Outbound Whatever time source the host already uses, commonly UDP 123 NTP Certificate validity — and signed-dispatch freshness — is judged against this clock Continuously

The runner's control connection#

A runner opens one long-lived outbound connection. On first connect it exchanges its enrollment key for a token (POST /runner/register), then upgrades to a WebSocket at /runner/socket/websocket, sending the token as a bearer during the upgrade. Every dispatch, result, and heartbeat rides that one connection.

  • TLS 1.2 or newer, validated against the host trust store. Loopback HTTP is accepted without a flag. The runner rejects cleartext HTTP and WebSocket connections to non-loopback hosts unless cloud.allow_insecure is set — that sends runner credentials without TLS, so use it only on an isolated development network.
  • Redirects are not followed. Registration and the socket upgrade both stop at a 3xx rather than chase the bearer token to whatever host answered. A captive portal or a proxy that answers with a redirect therefore fails closed instead of leaking a credential.
  • The runner reconnects on its own. After a drop it retries with backoff and advertises again. A connection timeout often means outbound port 443 is filtered — use emisar doctor to check that the control plane is reachable.

Installing and upgrading binaries#

Both installers download releases from https://emisar.dev/releases over emisar.dev:443 and verify the selected archive against its checksum. When GitHub CLI with gh attestation verify --bundle is installed, they also authenticate the signed checksum metadata. That optional check needs outbound HTTPS to tuf-repo-cdn.sigstore.dev:443 and tuf-repo.github.com:443, but no GitHub login.

To permit the optional GitHub release fallback, also allow outbound HTTPS to api.github.com:443, github.com:443, and release-assets.githubusercontent.com:443. An unavailable primary artifact may be fetched from that fallback. A malformed manifest, invalid signature, or checksum mismatch stops the install instead of switching sources.

A custom EMISAR_REPO uses GitHub directly. For a disconnected host, first save the trusted roots on a connected staging machine:

Run on the staging machine
$ gh attestation trusted-root > trusted_root.jsonl

Transfer that file with the selected archive, combined checksum, and *.sigstore.jsonl bundle. Pass it to the manual bundle check as --custom-trusted-root trusted_root.jsonl, verify the exact archive entry, then install the binary manually. The installers do not accept a custom trusted-root path. The Linux host page covers the surrounding setup, and Upgrade runners page owns the upgrade path.

The pack registry#

emisar pack install and emisar pack update fetch from the registry configured by --registry or EMISAR_PACKS_REGISTRY, defaulting to https://emisar.dev. By default, a pack fetch redirects to the immutable tarball on registry.emisar.dev. The runner follows only HTTPS redirects and rejects a cleartext downgrade. Allowlist both hosts.

These commands look packs up by name, and the public emisar origin serves those lookups. A static tree built with packctl has a different layout — install from its exact immutable tarball URL instead of passing that host to --registry. Running one is covered in Host your own registry.

The MCP bridge#

The LLM client starts the bridge as a child process and talks to it over stdin and stdout — no port, no socket. The bridge sends one HTTPS POST to /api/mcp/rpc per JSON-RPC frame, with no server-sent events, WebSocket, or open network session.

  • Requests can take a while on purpose. A wait can hold a request for up to a minute on the control plane, and the bridge allows 90 seconds before it gives up. An intermediary that cuts idle HTTP requests shorter than that will break waits while ordinary calls keep working — a confusing failure worth ruling out early.
  • It refuses to put your key on the wire in the clear. Loopback HTTP is accepted without an override; a cleartext http:// URL to any other host is rejected unless insecure transport is explicitly enabled — that sends the key without TLS and is only for an isolated development network. Redirects are refused for the same reason.
  • Cloud clients need nothing from you. Claude.ai and ChatGPT reach the hosted control plane themselves over OAuth. No bridge runs on your machines for those connections, so there is no egress rule to write.

Provider and action traffic#

A pack action runs a real command on the host, and that command talks to its own systems — Nomad, a database, a cloud API — directly. The traffic never passes through emisar, so build each pack's allowlist from the pack itself: emisar pack info <id> lists the binaries it needs and the environment variables it authenticates with, and their values carry the endpoints.

Proxies and TLS inspection#

The runner and the bridge use the platform's standard HTTP client, with no emisar-specific proxy, CA bundle, certificate pinning, or client certificate settings.

  • Proxy variables come from the process environment. HTTP_PROXY, HTTPS_PROXY, and NO_PROXY apply. On a systemd host, put them in /etc/emisar/runner.env. For the bridge, define them in the LLM client environment.
  • Every intermediary must pass the Upgrade header and keep the connection open. The runner uses a long-lived WebSocket. An appliance that strips the Upgrade header, buffers frames, or closes idle connections makes the runner reconnect over and over — often with no clear error to find.
  • TLS interception is a host trust-store question. If you terminate and re-sign TLS, the host has to trust the interception CA the same way it trusts any CA. emisar needs no configuration for this, and accepts only certificates the host trust store validates.

DNS and the clock#

A runner resolves the emisar origin on every connection, not only at boot. It resolves the registry during pack installs and GitHub only when the release fallback is used. Split-horizon DNS can send some hosts to an unreachable internal address. This often explains why one host works and another fails.

Keep the clock synchronized. TLS judges certificate validity against the host clock, and signed dispatch checks signature freshness against it too — so one skewed host can refuse actions while the rest of the fleet works.

Check your egress rules#

The product's own commands are the test — each fails in a recognizable place when a rule is missing:

shell
# on the runner host — config, credential, packs, action binaries, reachability
$ sudo emisar doctor

# on the workstation that runs your LLM client
$ emisar-mcp --version

The egress path is open when all of these are true:

  • emisar doctor passes — the host side works. The runner appears online in the console — the WebSocket connection made it through.
  • That runner advertises the packs and actions you installed. If a pack never downloaded, fix the registry rule.
  • If the host runs a pack that talks to a provider, one low-risk read-only action from it succeeds. Provider traffic leaves your host directly, so a real action is the end-to-end check for that leg.
  • emisar-mcp --version works on the workstation — the binary is installed. One LLM discovery call lists runners or finds an action — the bridge can reach the control plane.

Last reviewed September 3, 2026