Skip to content
Guides · Agent cookbook

Run an Agent's Shell Commands in a Remote Sandbox Over SSH

Updated November 1, 2026 .md

Short answer: Add a Host entry to ~/.ssh/config that tunnels to the sandbox through sbx proxy, then give your framework's SSH backend that host name. It runs each command as ssh host 'cmd'. Add ControlMaster to reuse one connection, which cut each command from about 0.53 s to 0.07 s in our 2026-11-01 test.

Many agent frameworks let you choose where shell commands run: on your machine, in a container, or on a host over SSH. The SSH option needs only a host name your system ssh can reach without a prompt. This page sets that up for a Sandboxer sandbox, then tests what an agent's SSH backend actually does: one-shot commands, a working directory, environment variables, long jobs, file transfer, and a stop and resume. Everything below ran on prod on 2026-11-01 from a Linux machine with OpenSSH 10.2, against a small sandbox.

For the general SSH setup and editors, see SSH into a cloud sandbox from VS Code or Cursor. For running a coding agent inside the VM instead, see Run Codex CLI on a remote machine.

1. Create the sandbox and a host entry#

sbx new --type small
sbx ssh-config          # prints the stanza the CLI's tunnel needs

sbx ssh-config prints a Host *.sbx block. Copy its lines into a named entry for your agent, so the framework only needs one word as its host. Paths must be absolute on your machine (here /home/you and /usr/local/bin/sbx):

# ~/.ssh/config
Host agent-box
  HostName sb_spayp0bfj4.sbx
  User user
  IdentityFile /home/you/.ssh/sandboxer_ed25519
  IdentitiesOnly yes
  ProxyCommand /usr/local/bin/sbx proxy %h
  UserKnownHostsFile /home/you/.config/sandboxer/known_hosts
  StrictHostKeyChecking yes
  ServerAliveInterval 30
  ServerAliveCountMax 3
  BatchMode yes
  ControlMaster auto
  ControlPath ~/.ssh/cm-%C
  ControlPersist 10m

What the extra lines do:

  • ProxyCommand sbx proxy %h carries SSH over an authenticated WebSocket, so only outbound HTTPS is needed. Before each connection it authorizes the CLI key and pins the sandbox's host key (SSH docs).
  • BatchMode yes makes ssh fail instead of waiting for a password or a host key prompt that no agent will answer.
  • ControlMaster, ControlPath and ControlPersist keep one connection open and run later commands over it.

The ProxyCommand uses the CLI's saved sign-in, or SANDBOXER_API_KEY if it's set in the environment that starts ssh. Run sbx login once on the machine where the framework runs.

2. Run commands the way an SSH backend does#

An SSH backend runs each tool call as a non-interactive ssh host 'command' and reads stdout, stderr and the exit code:

ssh agent-box 'whoami; pwd; uname -sr; echo $SHELL'
ssh agent-box 'exit 7'; echo "exit=$?"
user
/home/user
Linux 6.18.56-sandboxer
/bin/bash
exit=7

The remote exit code comes back unchanged, so the framework can tell a failed test from a passing one.

3. Measure: with and without ControlMaster#

We ran ssh <host> true ten times in a row, once against an entry without the Control* lines and once with them (milliseconds, 2026-11-01):

Entry Run 1 Runs 2–10
No ControlMaster 528 525–537
ControlMaster auto 527 66–68

Without it, every command pays for a new tunnel, key exchange and authorization, about half a second. With it, only the first command does. An agent that runs 200 shell commands in a session saves well over a minute of waiting. For comparison, sbx exec <id> true took about 107 ms per call and sbx ssh <id> true about 535 ms.

The sandbox's SSH server allows 10 sessions per connection (MaxSessions 10). When we started 12 commands at once over one master, 10 used it. The other 2 printed Session open refused by peer, fell back to their own connections, and still succeeded. If your framework runs many commands in parallel, expect that warning on stderr.

4. Working directory and environment variables#

Each ssh host 'cmd' starts a new shell. Nothing you set in one command is there in the next:

ssh agent-box 'mkdir -p proj && cd proj && pwd'
ssh agent-box 'pwd'
ssh agent-box 'export FOO=bar; echo FOO=$FOO'
ssh agent-box 'echo FOO=${FOO:-unset}'
/home/user/proj
/home/user
FOO=bar
FOO=unset

Frameworks handle this in one of two ways. Some track the directory themselves and prefix every command with cd <dir> &&. Others keep one remote shell open and write commands to its stdin, so state carries over:

printf 'cd proj\nexport STEP=2\npwd\necho STEP=$STEP\n' | ssh agent-box bash -s
/home/user/proj
STEP=2

For variables every command needs, these worked:

Method Result in our test
export line at the very top of ~/.bashrc Seen by every ssh host 'cmd'
export lines in a file, run as . ~/.agent_env && cmd Works; explicit per command
ssh -o SetEnv=NAME=value Not passed: the server only accepts LANG and LC_*

The ~/.bashrc line must go above the case $- in check, because the default file returns early for non-interactive shells. Keep secrets out of these files, since the agent can read them. Keep API keys out of an agent sandbox covers that.

5. Long-running commands#

A plain ssh host 'cmd' has no time limit of its own. A command that printed nothing for 150 seconds still returned normally:

ssh agent-box 'sleep 150; echo done-after-sleep'
done-after-sleep

Two details matter when a framework enforces its own timeout by killing ssh:

  • Without a terminal (the default for ssh host 'cmd'), the remote command kept running after we killed the local ssh with timeout 3. It finished and wrote its file 8 seconds later.
  • With -tt, killing the local ssh also ended the remote command.

To stop a remote command on time, put the limit on the remote side: ssh agent-box 'timeout 300 npm test'.

For jobs that should outlive the connection, detach them. Redirect stdin, stdout and stderr so ssh can return right away:

ssh agent-box 'nohup bash -c "sleep 20; echo built > ~/build.out" > ~/build.log 2>&1 < /dev/null &'
ssh agent-box 'tmux new -d -s job "for i in 1 2 3; do echo tick \$i; sleep 1; done; sleep 600"'
ssh agent-box 'tmux capture-pane -p -t job | head -3'
tick 1
tick 2
tick 3

The nohup line returned in 525 ms and ~/build.out held built later. tmux is installed in the sandbox. Long-running tasks covers the CLI's own detached commands.

6. Move files with rsync and scp#

rsync is installed in the sandbox, and both tools read the same host entry:

rsync -a --stats ./app/ agent-box:app/
scp agent-box:app/blob.bin ./

We synced a folder of 201 files (20 MB) and then changed one file (2026-11-01):

Transfer Time
First rsync upload, 201 files, 20 MB 0.79 s
Second rsync after one small change 0.28 s, 1 file sent
scp upload, 20 MB file 1.3 s

For a one-off copy without a host entry, sbx scp does the same job (SSH docs).

7. What happens after stop and resume#

We stopped the sandbox with sbx stop --wait while the ControlMaster connection was open:

  • While stopped, a new ssh agent-box failed in about 0.1 s with machine_not_running and exit code 255. But a command sent over the still-open master hung: we cut it off after 20 seconds.
  • After a short stop (under a minute), the old master carried commands again after sbx resume, at the same 67 ms.
  • After a 130-second stop, the master had already closed itself (ServerAliveInterval 30 × ServerAliveCountMax 3 is about 90 seconds). The next command opened a new one in 664 ms.
  • Host key: the same SHA256:u+K3hN… fingerprint before and after both resumes, and memory was restored (memoryRestored: true), so the tmux job from step 5 was still listed.

So an agent should not send commands to a stopped sandbox. Have the orchestrator resume it first (sbx resume <id>), or remove the master with ssh -O exit agent-box before a stop. A fork is a different sandbox with new SSH host keys, so give it its own host entry.

Checklist for any framework's SSH backend#

Setting Value for a sandbox
Host A named entry like agent-box in ~/.ssh/config, with HostName <id>.sbx
User user (home /home/user, sudo available)
Key ~/.ssh/sandboxer_ed25519, created and authorized by the CLI
Port None with the ProxyCommand tunnel; for the direct route, the port from sbx info's SSH endpoint
Proxy ProxyCommand /path/to/sbx proxy %h, with sbx signed in on that machine
Prompts BatchMode yes; host key pinned by sbx proxy
Keepalive ServerAliveInterval 30, ServerAliveCountMax 3
Speed ControlMaster auto, ControlPath ~/.ssh/cm-%C, ControlPersist 10m
Timeouts Wrap commands in remote timeout; killing local ssh doesn't stop them

If the framework asks for host, port, user and key path instead of a host alias, use the IPv4 sshEndpoint from sbx info with user and the key path. That port belongs to the running sandbox, so check it again after a resume. In our test it stayed the same.

FAQ#

Do I need to open a port or copy a key into the sandbox?#

No. The sbx proxy tunnel uses outbound HTTPS. Each connection authorizes the CLI's key and pins the host key, so StrictHostKeyChecking yes holds without a prompt.

Why is ssh slower than sbx exec?#

Each new SSH connection sets up a tunnel, a key exchange and authorization, about 0.53 s in our test. sbx exec is a single API call, about 0.11 s. With ControlMaster, SSH commands after the first took about 0.07 s.

Does the agent's working directory carry over between commands?#

Not with one ssh host 'cmd' per command: each starts in /home/user. Prefix commands with cd <dir> &&, or keep one ssh host bash -s session open and feed it commands.

Will a stopped sandbox wake up when the agent connects?#

No. A new connection fails at once with machine_not_running, and a command over an existing ControlMaster hangs. Resume the sandbox first with sbx resume <id>.

Can I run several agents against one sandbox?#

Yes, but one ControlMaster connection carries at most 10 sessions. Extra commands open their own connections and print a warning. For separate workspaces, give each agent its own sandbox and host entry.

More guides

Top AI sandbox platforms for code execution in 2026, ranked

For running AI-written code we rank Sandboxer, Freestyle and E2B first: each gives code its own VM with an API or SDK and can pause with memory. Pick Daytona for Windows or GPUs, Modal or Northflank for GPUs at scale, E2B or Northflank to run in your own cloud, and Vercel Sandbox if your app lives on Vercel.

Read the guide
Best sandboxes for running coding agents (Claude Code, Codex, Gemini CLI) in 2026, ranked

For coding agents we rank Sandboxer first: a full Ubuntu VM with Claude Code and Codex preinstalled, Docker, and stop-and-resume that keeps the agent's session in memory. Freestyle and Amp Orbs also keep memory across pauses. Pick E2B for prebuilt agent templates on open source, Docker Sandboxes for free local runs, and Codespaces if you live in GitHub.

Read the guide
Best Daytona sandbox alternatives in 2026, ranked

The best Daytona alternatives depend on why you're switching. For a full Linux microVM on every sandbox with memory-saving stop and Docker, we rank Sandboxer first. E2B suits open source and self-hosting, Modal massive short-job fleets, Northflank bring-your-own-cloud with GPUs, and Vercel Sandbox apps on Vercel.

Read the guide
Best E2B alternatives in 2026, ranked

If you're leaving E2B for longer sessions or a fuller machine, we rank Sandboxer first, then Freestyle: both give each sandbox a full Linux VM that keeps memory across pauses, with Docker and no short session cap. Pick Daytona for Windows or GPUs, Northflank for bring-your-own-cloud, and stay on E2B if you need open source.

Read the guide