Skip to main content
Sailboxes are closed to inbound traffic by default for security.

Inbound access control

To expose a service at creation time, pass the guest port in the create request, start a process that listens on that port, and wait for the listener endpoint to become ready:
You can also add and remove ports on a running Sailbox with expose/unexpose (see Add or remove ports at runtime). Sail supports two inbound protocols:
  • HTTP, which returns a public HTTPS URL and supports HTTP and WebSocket traffic.
  • Raw TCP, which returns a public host and port for protocols such as SSH, Postgres, or custom TCP servers.
Terminating a Sailbox also removes all of its listeners.

HTTP and WebSocket services

Expose a guest port over HTTP (in Python, a bare port number in ingress_ports means HTTP). The listener resolves to a routable HTTPS URL.
Use the same URL for WebSocket clients by replacing https:// with wss:// when the process inside the Sailbox speaks WebSocket on that port.

Raw TCP ports

Expose a port as raw TCP instead of HTTP when the protocol is not HTTP-aware or the service already implements its own authentication. The listener resolves to a public host and port that any TCP client can dial directly.
After the service starts, wait for the endpoint and connect with the matching client:

SSH access

For a quick interactive shell, use Sailbox.shell() or sail box shell: it streams a terminal over the same channel as exec, uses no ingress port, and while open it forwards the Sailbox’s localhost servers and browser opens to your machine. Set up SSH when you want a standard SSH endpoint: a devbox, your own client, scp, or port forwarding. It exposes guest port 22 as raw TCP, which counts against your org’s raw-TCP endpoint limit. SSH access is organization-scoped: a Sailbox trusts your org’s SSH certificate authority, so anyone in the org can connect with a short-lived certificate signed for their key. There are no per-Sailbox keys to manage. A private Sailbox is the exception: its SSH server accepts only its creator’s certificates. Enable SSH at create time, or with enable_ssh() on a Sailbox that already exists. Both install the org CA as trusted, start sshd, and expose port 22. enable_ssh() is idempotent; re-run it to bring sshd back up if it stops.
To connect, wire up your machine with the sail box ssh CLI and use the Sailbox’s <name>.sail shortcut:
alias fetches your certificate and writes the shortcut into your SSH config. The shortcut is what presents the certificate, so alias a Sailbox before you connect. Run it on each machine you connect from, whether you or a teammate enabled the Sailbox; it only touches local config and never wakes or changes the Sailbox. sail box ssh enable <id> enables SSH on an existing Sailbox and runs alias for you in one step, as does creating a Sailbox with sail box create --enable-ssh. To restrict which sources may connect, pass an allowlist of addresses or ranges: sb.enable_ssh(allowlist=["203.0.113.0/24"]) in Python, box.enableSsh({ allowlist: ["203.0.113.0/24"] }) in TypeScript, the allowlist argument of enable_ssh in Rust, or sail box ssh enable <id> --allowlist 203.0.113.0/24 from the CLI. A new allowlist replaces the current one, so re-running can tighten or relax access. Disabling SSH (sail box ssh disable) removes the port-22 listener along with its restriction. Your ssh-agent is the program on your computer that holds your SSH keys. Forwarding it lets programs on the box act as you toward other services, for example git push to GitHub, without copying any key onto the box. While it is forwarded, anything running on the box can use it, so Sail forwards it only to a private devbox you created. Your org’s administrators can still get root on any Sailbox, and that access is logged. The generated <name>.sail shortcut sets ForwardAgent no, which tells ssh not to forward your ssh-agent, even if your own SSH config forwards by default. That is because a Sailbox can be reachable by other people in your org. The one exception is a private devbox you created: its shortcut sets ForwardAgent yes so tools on it, like git, can act as you while you are connected. To change this for a single connection, pass the options yourself:
ControlPath=none is needed because ssh can reuse one underlying connection for several sessions. A session that joins a reused connection keeps whatever forwarding that connection started with, and your option is silently ignored. ControlPath=none opens a fresh connection so the option takes effect. If your config keeps connections open with ControlPersist, close a shared one with ssh -O exit <name>.sail; the box can reach your ssh-agent until that connection closes, not just until you disconnect. The SSH server persists across sleep and resume. An open SSH session drops when the Sailbox sleeps, but reconnecting with ssh <name>.sail wakes it and uses the same host key.

Inspect endpoints

Look up one listener when you know the guest port, or list all published ports for a Sailbox:
HTTP listeners expose a public URL. TCP listeners expose a public host and port.

Add or remove ports at runtime

You don’t have to declare every port at create time. expose publishes a new port on a running Sailbox and unexpose removes one, with no guest restart. The listener returned by expose carries the resolved endpoint but an "unknown" route status: the response confirms configuration, not reachability. wait_for_listener confirms the route is live. The HTTP API exposes the same two operations if you are not using an SDK.
Re-exposing a port under the same protocol sets its allowlist to what you pass, so pass the whole list every time. Passing none clears the restriction and reopens the port. Use it to tighten or relax a live port. Unexposing a raw-TCP port stops serving it, so it no longer counts against your org’s raw-TCP endpoint limit. Its public host:port stays owned by your org. Sail may reassign an idle address to another of your Sailboxes; it is never given to another org. If the address has not been reassigned, re-exposing the same guest port reclaims it exactly. Otherwise the re-expose allocates a new address, so read the endpoint from the response instead of assuming the old one. A stale client that dials an old raw-TCP address may therefore reach a different Sailbox in your org, and never another org’s. A raw-TCP guest port can’t be repurposed to HTTP; use a different guest port. HTTP ports carry no such reservation and are released on unexpose. expose and unexpose work on a paused or sleeping Sailbox without waking it; a later resume serves the new listener. From the CLI:

Access controls

A raw-TCP port is reachable from the public internet with no platform-side authentication. The in-guest daemon, such as sshd, is the only access control, so make sure it requires credentials.
To restrict which sources may connect, pass allowlist, as below. An entry that reads as an address or a range matches source IPs on HTTP and TCP listeners. Every other entry is a Sail app name, which admits authenticated traffic from Sailboxes in that app and is supported on HTTP listeners only. An app name cannot read as an address or a range, and cannot contain a /. An address must not carry an IPv6 zone, such as fe80::1%eth0, which names an interface on one machine rather than a source.
Connections from outside the listed addresses and ranges, or from authenticated Sailbox-origin requests whose app name is not listed, fail before reaching the guest. App names do not need to exist when you configure the listener. Cross-organization app-name matches are denied. An empty or omitted allowlist means any source may connect. For HTTP requests from one Sailbox to an app-name allowlisted listener, include the SDK-provided source headers. Inside the calling Sailbox, the Python SDK reads its own identity:
From outside a Sailbox, such as an orchestrator or test driving Sailboxes from the host, fetch the same headers for a specific live Sailbox you own (requires an organization-scoped API key):
Raw-TCP connections do not carry source app identity, so app-name entries are rejected on "tcp" listeners: a raw-TCP allowlist must contain only addresses and ranges. Exposing a well-known unauthenticated service port, such as Postgres, MySQL, or Redis, as raw TCP without an explicit allowlist is rejected. Set source restrictions, or use the all-sources allowlist to confirm you want it publicly reachable:

Sleeping services

Sleeping Sailboxes wake on network ingress. If you call sleep() on a Sailbox with exposed listeners, the next inbound HTTP, WebSocket, or TCP connection wakes the VM before forwarding traffic to the guest process.
Use pause() instead when you want to preserve VM state without waking on network traffic.

Ports and cleanup

Ports must be unique within a Sailbox and between 1 and 65535. Ports 10000, 10001, 15001, and 15002 are reserved by Sail. Port 22 is the SSH port and cannot be exposed as HTTP; expose it as raw TCP instead. Each org can hold a limited number (32) of concurrent raw-TCP endpoints. The limit counts actively-exposed endpoints, so unexpose frees a slot. An idle host:port stays owned by your org, even after the Sailbox that used it terminates. Sail may reassign it to another of your Sailboxes; another org never receives it. Contact us to raise your limit if you need more concurrent endpoints.

Custom domains

An HTTP listener can also answer on a hostname you own, such as app.example.com, with a TLS certificate that Sail obtains and renews for you. See Custom domains.