Reconnecting
Restoring connection…
Reconnecting
Restoring connection…
Docs navigation
Get started
Connect an LLM
Deploy runners
Team & account
Access
Identity providers
Account
Govern actions
Operate
Network requirements
What a runner host and a workstation have to reach on the way out — and why nothing has to reach in.
-
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
3xxrather 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 doctorprobes 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, andNO_PROXYapply. 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
Upgradeheader, 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.
# 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 doctorpasses, 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 --versionanswers 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