|
| 1 | +--- |
| 2 | +title: "SSH Access" |
| 3 | +description: "Connect to your sandbox via SSH using a WebSocket proxy" |
| 4 | +--- |
| 5 | + |
| 6 | +<Warning> |
| 7 | +This is a manual workaround. Native SSH support is planned but not yet available in E2B. |
| 8 | +This method uses websocat to proxy SSH connections over WebSocket through the sandbox's exposed ports. |
| 9 | +</Warning> |
| 10 | + |
| 11 | +SSH access enables remote terminal sessions, SCP/SFTP file transfers, and integration with tools that expect SSH connectivity. This guide shows how to create a template with SSH server and WebSocket proxy pre-configured. |
| 12 | + |
| 13 | +<Note> |
| 14 | +For simpler interactive terminal use cases, consider using the [PTY module](/docs/sandbox/pty) instead—it provides real-time bidirectional terminal communication without external SSH clients. |
| 15 | +</Note> |
| 16 | + |
| 17 | +## How it works |
| 18 | + |
| 19 | +``` |
| 20 | +┌─────────────────────────────────────────────────────────────┐ |
| 21 | +│ Your Machine │ |
| 22 | +│ ┌──────────┐ ProxyCommand ┌──────────────────┐ │ |
| 23 | +│ │ SSH │ ──────────────────── │ websocat / curl │ │ |
| 24 | +│ │ Client │ │ (WebSocket) │ │ |
| 25 | +│ └──────────┘ └────────┬─────────┘ │ |
| 26 | +└─────────────────────────────────────────────┼──────────────┘ |
| 27 | + │ |
| 28 | + wss://8081-<sandbox-id>.e2b.app |
| 29 | + │ |
| 30 | +┌─────────────────────────────────────────────┼──────────────┐ |
| 31 | +│ E2B Sandbox ▼ │ |
| 32 | +│ ┌──────────────────┐ │ |
| 33 | +│ │ websocat │ │ |
| 34 | +│ │ (WS → TCP:22) │ │ |
| 35 | +│ └────────┬─────────┘ │ |
| 36 | +│ │ │ |
| 37 | +│ ┌────────▼─────────┐ │ |
| 38 | +│ │ SSH Server │ │ |
| 39 | +│ │ (OpenSSH) │ │ |
| 40 | +│ └──────────────────┘ │ |
| 41 | +└─────────────────────────────────────────────────────────────┘ |
| 42 | +``` |
| 43 | + |
| 44 | +The WebSocket connection tunnels through E2B's port forwarding (`8081-<sandbox-id>.e2b.app`), and websocat inside the sandbox bridges it to the local SSH server. |
| 45 | + |
| 46 | +## Template definition |
| 47 | + |
| 48 | +<CodeGroup> |
| 49 | + |
| 50 | +```typescript JavaScript & TypeScript |
| 51 | +// template.ts |
| 52 | +import { Template, waitForPort } from 'e2b' |
| 53 | + |
| 54 | +export const template = Template() |
| 55 | + .fromUbuntuImage('22.04') |
| 56 | + .aptInstall(['openssh-server', 'wget']) |
| 57 | + // Download websocat binary |
| 58 | + .runCmd([ |
| 59 | + 'wget -qO /usr/local/bin/websocat https://github.com/vi/websocat/releases/latest/download/websocat.x86_64-unknown-linux-musl', |
| 60 | + 'chmod a+x /usr/local/bin/websocat', |
| 61 | + ]) |
| 62 | + // Configure SSH |
| 63 | + .runCmd([ |
| 64 | + 'mkdir -p /run/sshd', |
| 65 | + // Enable password auth (use SSH keys in production) |
| 66 | + "sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config", |
| 67 | + // Set default password (change this!) |
| 68 | + 'echo "user:sandbox" | chpasswd', |
| 69 | + ]) |
| 70 | + .copy('start_ssh.sh', '/start_ssh.sh') |
| 71 | + .runCmd('chmod +x /start_ssh.sh') |
| 72 | + .setStartCmd('/start_ssh.sh', waitForPort(8081)) |
| 73 | +``` |
| 74 | + |
| 75 | +```python Python |
| 76 | +# template.py |
| 77 | +from e2b import Template, wait_for_port |
| 78 | + |
| 79 | +template = ( |
| 80 | + Template() |
| 81 | + .from_ubuntu_image("22.04") |
| 82 | + .apt_install(["openssh-server", "wget"]) |
| 83 | + # Download websocat binary |
| 84 | + .run_cmd([ |
| 85 | + "wget -qO /usr/local/bin/websocat https://github.com/vi/websocat/releases/latest/download/websocat.x86_64-unknown-linux-musl", |
| 86 | + "chmod a+x /usr/local/bin/websocat", |
| 87 | + ]) |
| 88 | + # Configure SSH |
| 89 | + .run_cmd([ |
| 90 | + "mkdir -p /run/sshd", |
| 91 | + # Enable password auth (use SSH keys in production) |
| 92 | + "sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config", |
| 93 | + # Set default password (change this!) |
| 94 | + 'echo "user:sandbox" | chpasswd', |
| 95 | + ]) |
| 96 | + .copy("start_ssh.sh", "/start_ssh.sh") |
| 97 | + .run_cmd("chmod +x /start_ssh.sh") |
| 98 | + .set_start_cmd("/start_ssh.sh", wait_for_port(8081)) |
| 99 | +) |
| 100 | +``` |
| 101 | + |
| 102 | +</CodeGroup> |
| 103 | + |
| 104 | +```bash start_ssh.sh |
| 105 | +#!/bin/bash |
| 106 | + |
| 107 | +# Start SSH daemon |
| 108 | +/usr/sbin/sshd |
| 109 | + |
| 110 | +# Start WebSocket-to-SSH proxy on port 8081 |
| 111 | +exec websocat -b --exit-on-eof ws-l:0.0.0.0:8081 tcp:127.0.0.1:22 |
| 112 | +``` |
| 113 | + |
| 114 | +## Build the template |
| 115 | + |
| 116 | +<CodeGroup> |
| 117 | + |
| 118 | +```typescript JavaScript & TypeScript |
| 119 | +// build.ts |
| 120 | +import { Template, defaultBuildLogger } from 'e2b' |
| 121 | +import { template as sshTemplate } from './template' |
| 122 | + |
| 123 | +await Template.build(sshTemplate, 'ssh-ready', { |
| 124 | + cpuCount: 2, |
| 125 | + memoryMB: 2048, |
| 126 | + onBuildLogs: defaultBuildLogger(), |
| 127 | +}) |
| 128 | +``` |
| 129 | + |
| 130 | +```python Python |
| 131 | +# build.py |
| 132 | +from e2b import Template, default_build_logger |
| 133 | +from template import template as ssh_template |
| 134 | + |
| 135 | +Template.build(ssh_template, "ssh-ready", |
| 136 | + cpu_count=2, |
| 137 | + memory_mb=2048, |
| 138 | + on_build_logs=default_build_logger(), |
| 139 | +) |
| 140 | +``` |
| 141 | + |
| 142 | +</CodeGroup> |
| 143 | + |
| 144 | +## Quick start (CLI) |
| 145 | + |
| 146 | +You can also set this up manually without building a custom template: |
| 147 | + |
| 148 | +```bash |
| 149 | +# Create a sandbox from the base image |
| 150 | +e2b sbx create base |
| 151 | + |
| 152 | +# Inside the sandbox, install and start the SSH proxy |
| 153 | +sudo apt-get update && sudo apt-get install -y openssh-server |
| 154 | +sudo mkdir -p /run/sshd |
| 155 | +sudo wget -qO /usr/local/bin/websocat https://github.com/vi/websocat/releases/latest/download/websocat.x86_64-unknown-linux-musl |
| 156 | +sudo chmod a+x /usr/local/bin/websocat |
| 157 | + |
| 158 | +# Set a password for the user |
| 159 | +echo "user:sandbox" | sudo chpasswd |
| 160 | + |
| 161 | +# Start SSH and the WebSocket proxy |
| 162 | +sudo /usr/sbin/sshd |
| 163 | +websocat -b --exit-on-eof ws-l:0.0.0.0:8081 tcp:127.0.0.1:22 |
| 164 | +``` |
| 165 | + |
| 166 | +## Connect via SSH |
| 167 | + |
| 168 | +### Install websocat locally |
| 169 | + |
| 170 | +<Tabs> |
| 171 | +<Tab title="macOS"> |
| 172 | +```bash |
| 173 | +brew install websocat |
| 174 | +``` |
| 175 | +</Tab> |
| 176 | +<Tab title="Linux"> |
| 177 | +```bash |
| 178 | +# Download binary |
| 179 | +sudo wget -qO /usr/local/bin/websocat https://github.com/vi/websocat/releases/latest/download/websocat.x86_64-unknown-linux-musl |
| 180 | +sudo chmod a+x /usr/local/bin/websocat |
| 181 | +``` |
| 182 | +</Tab> |
| 183 | +</Tabs> |
| 184 | + |
| 185 | +### Connect using websocat |
| 186 | + |
| 187 | +```bash |
| 188 | +# Replace <sandbox-id> with your actual sandbox ID |
| 189 | +ssh -o 'ProxyCommand=websocat --binary -B 65536 - wss://8081-%h.e2b.app' <sandbox-id> |
| 190 | +``` |
| 191 | + |
| 192 | +<Note> |
| 193 | +The `<sandbox-id>` serves as both the SSH "host" and is interpolated into the WebSocket URL via `%h`. |
| 194 | +Default credentials: username `user`, password `sandbox`. |
| 195 | +</Note> |
| 196 | + |
| 197 | +### Connect using curl (alternative) |
| 198 | + |
| 199 | +If you have curl with WebSocket support (macOS Homebrew or recent Linux): |
| 200 | + |
| 201 | +```bash |
| 202 | +# macOS with Homebrew curl |
| 203 | +ssh -o 'ProxyCommand=/opt/homebrew/opt/curl/bin/curl --no-progress-meter -N --http1.1 -T . wss://8081-%h.e2b.app' <sandbox-id> |
| 204 | + |
| 205 | +# Linux with modern curl (7.86+) |
| 206 | +ssh -o 'ProxyCommand=curl --no-progress-meter -N --http1.1 -T . wss://8081-%h.e2b.app' <sandbox-id> |
| 207 | +``` |
| 208 | + |
| 209 | +### SSH config shortcut |
| 210 | + |
| 211 | +Add this to `~/.ssh/config` for easier access: |
| 212 | + |
| 213 | +```bash |
| 214 | +Host *.e2b |
| 215 | + User user |
| 216 | + ProxyCommand websocat --binary -B 65536 - wss://8081-%h.app |
| 217 | + StrictHostKeyChecking no |
| 218 | + UserKnownHostsFile /dev/null |
| 219 | +``` |
| 220 | + |
| 221 | +Then connect with: |
| 222 | + |
| 223 | +```bash |
| 224 | +ssh <sandbox-id>.e2b |
| 225 | +``` |
| 226 | + |
| 227 | +## SDK usage |
| 228 | + |
| 229 | +<CodeGroup> |
| 230 | + |
| 231 | +```typescript JavaScript & TypeScript |
| 232 | +import { Sandbox } from 'e2b' |
| 233 | + |
| 234 | +const sbx = await Sandbox.create('ssh-ready') |
| 235 | + |
| 236 | +const host = sbx.getHost(8081) |
| 237 | +console.log(` |
| 238 | +Sandbox ready! |
| 239 | +
|
| 240 | +Connect with: |
| 241 | + ssh -o 'ProxyCommand=websocat --binary -B 65536 - wss://${host}' user@localhost |
| 242 | +
|
| 243 | +Or with curl: |
| 244 | + ssh -o 'ProxyCommand=curl --no-progress-meter -N --http1.1 -T . wss://${host}' user@localhost |
| 245 | +
|
| 246 | +Password: sandbox |
| 247 | +`) |
| 248 | + |
| 249 | +// Keep sandbox alive - it will timeout based on your settings |
| 250 | +// await sbx.kill() // Call when done |
| 251 | +``` |
| 252 | + |
| 253 | +```python Python |
| 254 | +from e2b import Sandbox |
| 255 | + |
| 256 | +sbx = Sandbox("ssh-ready") |
| 257 | + |
| 258 | +host = sbx.get_host(8081) |
| 259 | +print(f""" |
| 260 | +Sandbox ready! |
| 261 | +
|
| 262 | +Connect with: |
| 263 | + ssh -o 'ProxyCommand=websocat --binary -B 65536 - wss://{host}' user@localhost |
| 264 | +
|
| 265 | +Or with curl: |
| 266 | + ssh -o 'ProxyCommand=curl --no-progress-meter -N --http1.1 -T . wss://{host}' user@localhost |
| 267 | +
|
| 268 | +Password: sandbox |
| 269 | +""") |
| 270 | + |
| 271 | +# Keep sandbox alive - it will timeout based on your settings |
| 272 | +# sbx.kill() # Call when done |
| 273 | +``` |
| 274 | + |
| 275 | +</CodeGroup> |
| 276 | + |
| 277 | +## Using SSH keys (recommended) |
| 278 | + |
| 279 | +For production use, configure SSH key authentication instead of passwords: |
| 280 | + |
| 281 | +<CodeGroup> |
| 282 | + |
| 283 | +```typescript JavaScript & TypeScript |
| 284 | +import { Sandbox } from 'e2b' |
| 285 | + |
| 286 | +const sbx = await Sandbox.create('ssh-ready') |
| 287 | + |
| 288 | +// Inject your public key |
| 289 | +const publicKey = 'ssh-rsa AAAA... your-key-here' |
| 290 | +await sbx.files.write('/home/user/.ssh/authorized_keys', publicKey) |
| 291 | +await sbx.commands.run('chmod 600 /home/user/.ssh/authorized_keys') |
| 292 | +await sbx.commands.run('chown -R user:user /home/user/.ssh') |
| 293 | + |
| 294 | +const host = sbx.getHost(8081) |
| 295 | +console.log(`Connect with: ssh -o 'ProxyCommand=websocat --binary -B 65536 - wss://${host}' user@localhost`) |
| 296 | +``` |
| 297 | + |
| 298 | +```python Python |
| 299 | +from e2b import Sandbox |
| 300 | + |
| 301 | +sbx = Sandbox("ssh-ready") |
| 302 | + |
| 303 | +# Inject your public key |
| 304 | +public_key = "ssh-rsa AAAA... your-key-here" |
| 305 | +sbx.files.write("/home/user/.ssh/authorized_keys", public_key) |
| 306 | +sbx.commands.run("chmod 600 /home/user/.ssh/authorized_keys") |
| 307 | +sbx.commands.run("chown -R user:user /home/user/.ssh") |
| 308 | + |
| 309 | +host = sbx.get_host(8081) |
| 310 | +print(f"Connect with: ssh -o 'ProxyCommand=websocat --binary -B 65536 - wss://{host}' user@localhost") |
| 311 | +``` |
| 312 | + |
| 313 | +</CodeGroup> |
| 314 | + |
| 315 | +To disable password authentication entirely, modify the template: |
| 316 | + |
| 317 | +```typescript |
| 318 | +.runCmd("sed -i 's/PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config") |
| 319 | +``` |
| 320 | + |
| 321 | +## File transfer with SCP |
| 322 | + |
| 323 | +Once SSH is working, you can use SCP for file transfers: |
| 324 | + |
| 325 | +```bash |
| 326 | +# Upload a file |
| 327 | +scp -o 'ProxyCommand=websocat --binary -B 65536 - wss://8081-%h.e2b.app' ./local-file.txt <sandbox-id>:/home/user/ |
| 328 | + |
| 329 | +# Download a file |
| 330 | +scp -o 'ProxyCommand=websocat --binary -B 65536 - wss://8081-%h.e2b.app' <sandbox-id>:/home/user/remote-file.txt ./ |
| 331 | +``` |
| 332 | + |
| 333 | +## Alternatives |
| 334 | + |
| 335 | +| Approach | Best for | |
| 336 | +|----------|----------| |
| 337 | +| **[PTY module](/docs/sandbox/pty)** | Interactive terminals via SDK, no external tools needed | |
| 338 | +| **[E2B CLI](/docs/cli/connect-to-sandbox)** | Quick terminal access during development | |
| 339 | +| **SSH (this guide)** | Integration with SSH-based tools, SCP/SFTP, remote IDEs | |
0 commit comments