Skip to main content
Docs navigation

Network requirements

What a runner host and a workstation have to reach on the way out — and why nothing has to reach in.

Before you start, you need:
  • The emisar origin you connect to. On the hosted control plane that is https://emisar.dev.
  • Whoever can change egress rules for the hosts you are about to install on.
  • The pack registry you install from, if it is not the default.

What has to be reachable#

Every network row is outbound from your host. Nothing here is an inbound rule — the last row says so explicitly.

Operation and component Direction Destination and port Protocol Why When required
Runner registration Outbound Your emisar origin, TCP 443 HTTPS POST /runner/register Trades the enrollment key for this runner's own long-lived token First connect, and again after an enrollment-key change
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 api.github.com, github.com and its release-asset host, TCP 443 HTTPS Resolves the release, then downloads the tarball and its checksums Only while install.sh or install-mcp.sh runs
Pack install and update Outbound The configured registry and the storage host it redirects to, TCP 443 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 spawns the bridge as a child process Whenever the client is running
Name resolution Outbound Your configured resolver DNS Resolves the emisar origin, and the GitHub and registry hosts On connect and each reconnect; also during install and update
Clock synchronisation Outbound Whatever time source the host already uses, commonly UDP 123 NTP Certificate validity — and signed-dispatch freshness — is judged against this clock Continuously
Anything reaching the runner host Inbound No listener, no port, no NAT rule The runner only ever dials out; nothing calls it Never

The runner's control connection#

A runner opens one long-lived outbound connection and keeps it. On first connect it trades its enrollment key for a token over POST /runner/register, then upgrades to a WebSocket at /runner/socket/websocket and carries its token as a bearer on the upgrade. Every dispatch, result, and heartbeat rides that one connection. It listens on nothing.

  • TLS 1.2 or newer, validated against the host's trust store. The runner requires a modern TLS floor and refuses cleartext to a non-loopback host unless you deliberately set cloud.allow_insecure — which exists for a loopback development portal, not for production.
  • Redirects are refused, 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.
  • Disconnects are expected; a blocked path is not. The runner reconnects with backoff and re-advertises. A connect that times out rather than being refused usually means outbound 443 is filtered — emisar doctor probes reachability as one of its checks.

Installing and upgrading binaries#

Both installers resolve and download releases from GitHub, so they need more than your emisar origin — but only while they run. They ask api.github.com for the release, then download the tarball and its checksums from github.com, which redirects to GitHub's release-asset host. Allow the redirect target too, or the download stops with a checksum file it never fetched.

On a host that cannot reach GitHub at all, download the release tarball elsewhere and place the binary yourself — see Install on a host for what the installer sets up around it, and Upgrade runners and MCP bridges for 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. That default answers a pack fetch with a redirect to the immutable tarball on registry.emisar.dev, and the runner follows it — over HTTPS only, refusing a downgrade to cleartext. Allowlist both.

A private registry is any static host serving the registry layout, so its origin is the one you allow instead. Running one is covered in Host your own registry.

The MCP bridge#

The bridge is a child process of your LLM client. The client talks to it over stdin and stdout — no port, no socket — and the bridge makes one plain HTTPS POST per JSON-RPC frame to /api/mcp/rpc. There is no server-sent-event stream, no WebSocket, and no session for a network appliance to hold open.

  • 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. A cleartext http:// URL to a non-loopback host is rejected at startup unless you explicitly override it; loopback is the one exception. It also refuses to follow a redirect, for the same reason the runner does.
  • 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's action runs a real command on the host, and that command talks to whatever it talks to — your Nomad cluster, a database, a cloud API. That traffic is between your host and your systems; it never goes through emisar, and emisar cannot publish one allowlist for it. Work it out per pack: emisar pack info <id> prints the binaries an installed pack needs and the environment variables its tool reads to authenticate, which is the shortest route to the endpoints it will reach.

Proxies and TLS inspection#

Neither the runner nor the bridge has an emisar-specific proxy setting, CA-bundle option, certificate pinning, or client-certificate configuration. Both build on the platform's standard HTTP client, so the process environment is where a proxy is configured and the host's own trust store is what validates certificates.

  • Proxy variables come from the process environment. HTTP_PROXY, HTTPS_PROXY, and NO_PROXY apply. On a systemd host the service reads /etc/emisar/runner.env, so that is where they go; for the bridge, set them wherever your LLM client's environment is defined.
  • Whatever sits in the path has to pass an Upgrade and hold the connection. The runner's connection is a long-lived WebSocket. An appliance that strips the Upgrade header, buffers frames, or idles the connection out shows up as a runner that keeps reconnecting rather than as an outright failure.
  • TLS interception is a host trust-store question. If you terminate and re-sign TLS, the interception CA has to be trusted by the host, in the ordinary way that host trusts CAs. There is nothing to configure in emisar, and nothing in emisar will bypass a certificate it cannot validate.

DNS and the clock#

A runner host resolves your emisar origin when it connects and again on every reconnect — not once at boot — and resolves the GitHub and registry hosts while it installs or updates packs. Split-horizon DNS that answers your emisar origin with an internal address only some hosts can reach is a common cause of "it works on one box."

Keep the clock synchronised. Certificate validity is judged against it, so a skewed host fails TLS at connect. If you use signed dispatch, the runner also judges a signature's freshness and its certificate's window against the same clock, so skew there surfaces as refused actions on one host while the rest of the fleet is fine.

What never needs an inbound rule#

Everything that looks like it should be inbound terminates on the hosted control plane instead of on your infrastructure: your identity provider's OIDC callback and its SCIM connector, remote MCP and OAuth from Claude.ai or ChatGPT, and the approval emails your team clicks. Those reach emisar, not you. A runner host needs no open port, no public address, and no NAT rule — which is what makes it deployable inside a network that does not accept inbound connections at all.

Runtime-specific networking — container namespaces, pod specs, Nomad host networks — stays with its deployment page: Run in a container, Kubernetes, and Nomad. If a runner will not connect, start with Runner fleet.

Prove the path works#

There is no emisar command that tests your firewall. What you have instead are the ordinary product checks, and they are enough: each one fails in a recognisable 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 hold:

  • emisar doctor passes, and the runner then shows online in the console — the first covers the host, the second proves the WebSocket upgrade survived whatever sits in the path.
  • That runner advertises the packs and actions you installed. A pack that never downloaded is a registry rule, not a runner problem.
  • If the host runs a pack that talks to a provider, one low-risk read-only action from it succeeds. There is no generic probe for that leg: provider traffic leaves your host directly, so the real action is the useful end-to-end check.
  • emisar-mcp --version answers on the workstation, and one discovery call from the LLM client — ask it to list runners or find an action — returns. The version proves the binary is there; only the call proves the bridge reached the control plane.

Last reviewed August 3, 2026

Suggest a change