---
title: SSH
description: Connect to sandboxes through SSH protocol sessions
icon: "computer"
---

SSH gives a sandbox a familiar interface without putting an SSH daemon inside the guest. microsandbox speaks SSH on the host side, then forwards shells, remote commands, and SFTP file operations through the sandbox's command and filesystem channels.

Use it when you want existing SSH tools to work with a sandbox, or when you want SDK code to use SSH semantics while still going through microsandbox.

There are two ways to connect:

- **Native sessions** use the built-in microsandbox SSH client. No host `ssh` binary, TCP listener, or authorized key is required, which makes it the quickest path for a shell from `msb` or SSH semantics from the SDK.
- **External clients** serve the sandbox as an SSH server so standard tools such as `ssh`, `sftp`, and `ProxyCommand` can connect.

## Native sessions

Native sessions keep the SSH protocol boundary but do not expose a listener. microsandbox creates an in-process SSH client/server pair over an in-memory stream, then forwards SSH channels to the sandbox.

### CLI

```bash
msb ssh devbox
msb ssh connect devbox
```

With no remote command, the CLI opens an interactive shell. With `--`, the remaining tokens become the SSH command string and run through the sandbox shell.

```bash
msb ssh devbox -- uname -a
msb ssh devbox -- sh -lc "cd /app && npm test"
```

If a sandbox name collides with an SSH subcommand such as `serve` or `authorize`, use `--name`. Sandbox names are limited to 128 UTF-8 bytes:

```bash
msb ssh --name serve
msb ssh --name authorize -- uptime
```

### SDK

<CodeGroup>
```rust Rust
let sb = Sandbox::start("devbox").await?;
let ssh = sb.ssh().connect().await?;

let output = ssh.exec("uname -a").await?;
println!("{}", String::from_utf8_lossy(&output.stdout));

ssh.close().await?;
```

```typescript TypeScript
const sb = await Sandbox.start("devbox");
const ssh = await sb.ssh().connect();

const output = await ssh.exec("uname -a");
console.log(output.stdout.toString());

await ssh.close();
```

```python Python
sb = await Sandbox.start("devbox")
ssh = await sb.ssh().connect()

output = await ssh.exec("uname -a")
print(output.stdout_text)

await ssh.close()
```

```go Go
sb, err := m.StartSandbox(ctx, "devbox")
if err != nil {
    return err
}
defer sb.Close()

ssh, err := sb.SSH().Connect(ctx)
if err != nil {
    return err
}
defer ssh.Close(ctx)

output, err := ssh.Exec(ctx, "uname -a")
if err != nil {
    return err
}
fmt.Print(string(output.Stdout))
```
</CodeGroup>

### Interactive attach

SDK clients can attach the local terminal to an SSH shell. This requires a real terminal.

<CodeGroup>
```rust Rust
let code = sb
    .ssh()
    .connect_with(|ssh| ssh.term("xterm-256color"))
    .await?
    .attach()
    .await?;
```

```typescript TypeScript
const ssh = await sb.ssh().connect({ term: "xterm-256color" });
const code = await ssh.attach({ detachKeys: "ctrl-]" });
```

```python Python
ssh = await sb.ssh().connect(term="xterm-256color")
code = await ssh.attach(detach_keys="ctrl-]")
```

```go Go
ssh, err := sb.SSH().Connect(ctx, m.WithSSHTerm("xterm-256color"))
if err != nil {
    return err
}
code, err := ssh.Attach(ctx, m.WithSSHDetachKeys("ctrl-]"))
```
</CodeGroup>

### SFTP

The native SSH client can open SFTP over the same SSH connection.

<CodeGroup>
```rust Rust
let sftp = ssh.sftp().await?;
sftp.write("/tmp/hello.txt", b"hello").await?;
let data = sftp.read("/tmp/hello.txt").await?;
sftp.close().await?;
```

```typescript TypeScript
const sftp = await ssh.sftp();
await sftp.write("/tmp/hello.txt", Buffer.from("hello"));
const data = await sftp.read("/tmp/hello.txt");
await sftp.close();
```

```python Python
sftp = await ssh.sftp()
await sftp.write("/tmp/hello.txt", b"hello")
data = await sftp.read("/tmp/hello.txt")
await sftp.close()
```

```go Go
sftp, err := ssh.SFTP(ctx)
if err != nil {
    return err
}
defer sftp.Close(ctx)

if err := sftp.Write(ctx, "/tmp/hello.txt", []byte("hello")); err != nil {
    return err
}
data, err := sftp.Read(ctx, "/tmp/hello.txt")
```
</CodeGroup>

## External clients

External client mode exposes a sandbox as an SSH server for tools that already speak SSH. This is the closest match for normal SSH usage: authorize a public key, serve the sandbox, then connect with `ssh` or `sftp`.

### Authorize a key

```bash
msb ssh authorize --file ~/.ssh/id_ed25519.pub
msb ssh authorize --key "ssh-ed25519 AAAA... user@host"
cat ~/.ssh/id_ed25519.pub | msb ssh authorize --stdin
```

Keys are appended to `<MSB_HOME>/ssh/authorized_keys`. Existing keys are deduplicated by public-key material.

### TCP listener

```bash
msb ssh serve devbox
```

The default listener is `127.0.0.1:2222`.

```bash
ssh -p 2222 root@127.0.0.1
sftp -P 2222 root@127.0.0.1
```

Choose a different bind address or port when needed:

```bash
msb ssh serve devbox --host 127.0.0.1 --port 2223
```

<Note>
  Binding to `0.0.0.0` exposes the SSH listener beyond the local machine. Keep the default loopback bind unless you intentionally want remote clients to connect.
</Note>

### ProxyCommand

`msb ssh serve --stdio` carries a single SSH connection over stdin/stdout instead of a TCP listener. Point OpenSSH at it with `ProxyCommand` so any standard SSH tool can reach the sandbox by host alias, with no port to manage.

Add an entry to your `~/.ssh/config`:

```sshconfig ~/.ssh/config
Host devbox.msb
  User root
  ProxyCommand msb ssh serve devbox --stdio
```

The `Host` value is the alias you connect to, and `devbox` is the sandbox name passed to `msb ssh serve`. No `HostName` or `Port` is needed, because OpenSSH runs the `ProxyCommand` to open the transport instead of dialing a socket.

Once it is in place, the alias works with any OpenSSH-based tool:

```bash
ssh devbox.msb
sftp devbox.msb
scp ./build.tar devbox.msb:/tmp/
rsync -av ./src/ devbox.msb:/app/
```

Editors that build on OpenSSH, such as VS Code Remote-SSH, connect to the `devbox.msb` host the same way.

### SDK server endpoints

The Rust SDK exposes the general form: prepare an SSH server endpoint, then pass each ordered duplex stream to `serve(stream)`.

```rust
let server = sandbox
    .ssh()
    .server_with(|ssh| ssh.sftp(true))
    .await?;

server.serve(stream).await?;
```

The TypeScript, Python, and Go SDKs expose stdio server helpers for process-bridge use cases. See the SDK reference pages for the exact language-specific surface.

For exact SDK signatures, see the SSH reference for [Rust](/sdk/rust/ssh), [TypeScript](/sdk/typescript/ssh), [Python](/sdk/python/ssh), or [Go](/sdk/go/ssh).
