# SSH Access (/docs/ssh-access)

Daytona provides SSH access to sandboxes through an SSH gateway, so you can connect from local terminals, IDEs, and development tools without installing software in the sandbox.

SSH has no equivalent of a bearer header, so the credential travels in the SSH username: the sandbox ID and the credential, joined by a hash character. The credential is a sandbox access token that allows SSH, or a project API key. Connect to the SSH gateway of the sandbox's region:

```bash
ssh -p 2222 '<sandboxId>#<token>'@<sshGatewayHost>
```

The gateway splits the username on the first hash character, validates the credential for that sandbox, and proxies the session to the SSH server inside the sandbox. The sandbox ID part accepts either the region-tagged ID that the API returns or the bare UUID.

<SshAccessVisual />

The gateway accepts the SSH handshake without client authentication and authorizes afterwards, so a rejected connection is closed right after the handshake instead of failing with a password prompt. Connections are rejected when:

- The credential is invalid or expired, belongs to another project, or does not allow SSH to the sandbox
- The sandbox ID is unknown, or the sandbox lives in a different region than the gateway
- The sandbox is not `running`
- The username has no `#` separator, or either part is empty

The sandbox reports running once its toolbox port answers. The in-sandbox SSH server can lag behind by a few seconds after a create, so a connection opened immediately after the sandbox becomes running may be closed. Retry the connection.

<Callout type="note" title="Info">
The SSH gateway host is region-specific, and the examples on this page use `<sshGatewayHost>` in its place. Use the host shown in the Dashboard's **SSH** tab, or read `ssh_gateway_host` and `ssh_gateway_port` from the [sandbox details](#access-via-api).
</Callout>

## Use cases [#use-cases]

SSH access is used when a local tool needs a shell or a TCP connection inside a sandbox.

- **Remote development in an IDE.** VS Code and JetBrains IDEs connect over SSH, then edit and run code in the sandbox. JetBrains Gateway installs the selected IDE in the sandbox. Each IDE takes the SSH command from the Dashboard as its connection URL.

- **Local terminals.** A developer opens a shell in the sandbox from their own terminal, with no software installed in the sandbox. Resizing the local window resizes the remote shell. The session has no idle timeout of its own and stays open while the sandbox runs.

- **File transfer.** `scp` and `sftp` copy files between your machine and the sandbox with the standard OpenSSH clients. Relative paths resolve against the working directory, which is the image's `WORKDIR` or the home directory. A sandbox created with a default user uses that user's home directory instead.

- **Scripts and pipelines.** A one-shot command returns the exit code of the remote command, with stdout and stderr on separate streams. Input piped into `ssh` reaches the remote command. Closing stdin on the client half-closes the remote stdin, so piped commands end once they consume the input.

- **Sandbox services on your machine.** Local port forwarding connects a local port to a database or web server in the sandbox, without exposing it to the internet. The target address resolves inside the sandbox, so `localhost:8080` reaches a service bound there. Remote forwarding with `-R` is not supported.

## Access from Dashboard [#access-from-dashboard]

The [Daytona Dashboard ↗](https://app.daytona.com/dashboard/sandboxes) builds the SSH command for you.

1. Go to [Sandboxes ↗](https://app.daytona.com/dashboard/sandboxes)
2. Locate the sandbox you want to connect to
3. Select **SSH access** in the options menu (**⋮**), or the sandbox's **SSH** tab
4. Copy the **SSH command** and run it in your terminal

The command carries a sandbox access token that allows only SSH to this sandbox and expires after 15 minutes. The Dashboard creates a new token each time the SSH tab opens. The token is masked in the panel; use **Reveal token** to show it. Copying always yields the full command.

Only members whose project API key has sandbox write permission see the SSH tab.

## Access via CLI [#access-via-cli]

The Daytona CLI resolves the SSH gateway of the sandbox's region and runs the system SSH client with your credential in the username.

<Tabs syncKey="language">
<TabItem label="CLI" icon="seti:shell">

```bash
# Interactive shell
daytona sandbox ssh {sandboxId}

# One-shot command
daytona sandbox ssh {sandboxId} uname -a

# Forward a sandbox port to your machine
daytona sandbox ssh {sandboxId} -L 8080:localhost:8080
```

<Callout type="note" title="Notes">
- The request requires the `write:sandboxes` permission.
- The CLI requires OpenSSH `ssh` on your `PATH`.
- SSH flags after the sandbox ID go to `ssh` before the destination.
- SSH flags end at the first argument that does not start with `-` and is not the value of a flag such as `-L` or `-p`.
- Use `--` to end the SSH flags explicitly when the remote command itself starts with `-`.
- Any remaining arguments become the remote command.
</Callout>

</TabItem>
</Tabs>

## Access via API [#access-via-api]

The sandbox details include the SSH gateway endpoint for the sandbox's region:

```bash
curl 'https://api.daytona.com/sandboxes/{sandboxId}' \
  --header 'Authorization: Bearer YOUR_API_KEY'
```

Build the SSH command from the SSH gateway host and port fields in the response, the sandbox ID, and a [sandbox access token](#create-an-ssh-token) or your API key. Both fields are absent when the region's gateway cannot be resolved. Each region configures its own gateway host; a region without one uses the host of its toolbox proxy URL. The port defaults to 2222.

### Create an SSH token [#create-an-ssh-token]

Create a sandbox access token that allows only SSH to one sandbox. A token expires after its lifetime and cannot be renewed, so create a new one when it expires. Use a token instead of a project API key when you hand SSH access to a person, a CI job, or another tool.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
token = sandbox.create_access_token(operations=["ssh"], ttl_seconds=3600)
print(f"ssh -p 2222 '{sandbox.id}#{token.access_token}'@<sshGatewayHost>")
```

| **Parameter** | **Type**  | **Description**                                                                                |
| ------------- | --------- | ---------------------------------------------------------------------------------------------- |
| `operations`  | list\[str\] | Sets the operations the token allows. `["ssh"]` limits it to SSH.                          |
| `ttl_seconds` | int       | Sets the token lifetime in seconds, at most `86400`. The default is `3600`.               |

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
const token = await sandbox.createAccessToken({ operations: ['ssh'], ttlSeconds: 3600 })
console.log(`ssh -p 2222 '${sandbox.id}#${token.accessToken}'@<sshGatewayHost>`)
```

| **Parameter** | **Type**  | **Description**                                                                                |
| ------------- | --------- | ---------------------------------------------------------------------------------------------- |
| `operations`  | string\[\] | Sets the operations the token allows. `['ssh']` limits it to SSH.                          |
| `ttlSeconds`  | number    | Sets the token lifetime in seconds, at most `86400`. The default is `3600`.               |

</TabItem>
<TabItem label="API" icon="seti:json">

```bash
curl 'https://api.daytona.com/sandboxes/{sandboxId}/access-token' \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data '{"operations": ["ssh"], "ttl_seconds": 3600}'
```

| **Field**     | **Type**  | **Description**                                                                                |
| ------------- | --------- | ---------------------------------------------------------------------------------------------- |
| `operations`  | string\[\] | Sets the operations the token allows. `["ssh"]` limits it to SSH.                          |
| `ttl_seconds` | integer   | Sets the token lifetime in seconds. Defaults to 3600, and the maximum is 86400.               |

<Callout type="note" title="Notes">
- The request requires the `write:sandboxes` permission.
- Put the `access_token` field of the response after the hash character in the SSH username.
</Callout>

</TabItem>
</Tabs>

## Usage examples [#usage-examples]

The gateway forwards every SSH channel and channel request, so interactive shells, one-shot commands, scp, sftp, and local port forwarding work with the standard OpenSSH clients.

```bash
# Interactive shell
ssh -p 2222 '<sandboxId>#<token>'@<sshGatewayHost>

# One-shot command
ssh -p 2222 '<sandboxId>#<token>'@<sshGatewayHost> uname -a

# Pipe stdin into a remote command
cat ./input.txt | ssh -p 2222 '<sandboxId>#<token>'@<sshGatewayHost> wc -l

# Copy a file into the sandbox
scp -P 2222 ./file.txt '<sandboxId>#<token>'@<sshGatewayHost>:/work/

# Interactive file transfer
sftp -P 2222 '<sandboxId>#<token>'@<sshGatewayHost>

# Forward a sandbox port to your machine
ssh -p 2222 -L 8080:localhost:8080 '<sandboxId>#<token>'@<sshGatewayHost>
```

Exit codes are preserved, so one-shot SSH commands can be used in scripts and pipelines. Standard output and standard error arrive as separate streams, so redirecting standard error on the client side works as with any SSH host. Closing stdin on the client half-closes the remote command's stdin, so piped commands terminate once the input is consumed.

## Session environment [#session-environment]

An SSH session on a Linux sandbox lands in the SSH server run by the Daytona daemon:

| **Property**          | **Behavior**                                                                                                                                                        |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Shell**             | The first of `bash`, then `zsh`, listed in the sandbox's `/etc/shells`; `sh` when neither is listed. A sandbox created with a default `user` uses that user's login shell instead. Interactive sessions and one-shot commands use the same shell. |
| **Working directory** | The daemon's, which is the image's `WORKDIR` or, if none, the home directory. A sandbox created with a default `user` uses that user's home directory instead. One-shot commands run there, and relative `scp` and `sftp` paths resolve against it.  |
| **Terminal**          | Honors `pty-req` and `window-change`, so resizes reach the shell.                                                                                                   |
| **Idle timeout**      | None; the sandbox lifecycle, not the SSH server, ends idle sessions.                                                                                                |

## Port forwarding [#port-forwarding]

Local forwarding opens a TCP connection inside the sandbox for every connection to the local port. The target address is resolved inside the sandbox, so a target on localhost reaches a service in the sandbox, not on your machine.

- Local forwarding uses the `-L` flag. `-L 8080:localhost:8080` reaches a service bound to `localhost:8080` in the sandbox.
- A target port with nothing listening is refused with `open failed: connect failed`.
- An unresponsive target times out after 10 seconds.

```bash
# Reach a sandbox web server on http://localhost:8080
ssh -p 2222 -N -L 8080:localhost:8080 '<sandboxId>#<token>'@<sshGatewayHost>
```

The following are not supported through the gateway:

- **Remote forwarding (`-R`)**: rejected, with `remote port forwarding failed`.
- **Unix socket forwarding** (`-L`/`-R` with socket paths)
- **Agent forwarding (`-A`)**

To expose a sandbox port to the internet rather than to your machine, use [sandbox previews](/docs/preview).

## Connection lifetime [#connection-lifetime]

An SSH session stays open for as long as the sandbox is running. The gateway closes the client connection promptly when the sandbox stops running or is deleted.

Reconnecting with the same command attaches to the sandbox again. For long interactive sessions, wrap ssh in a reconnect loop or use autossh; set the ServerAliveInterval option so the client notices a dead connection quickly:

```bash
ssh -p 2222 -o ServerAliveInterval=3 -o ServerAliveCountMax=2 '<sandboxId>#<token>'@<sshGatewayHost>
```

SSH traffic counts toward the sandbox's [idle timeout](/docs/linux-sandboxes#idle-timeout). The gateway records activity once the in-sandbox SSH server has answered, and then whenever bytes flow from the client into the sandbox. A one-shot command run without a terminal keeps the sandbox active for as long as it runs. Otherwise an open session with no input does not keep the sandbox alive: a shell left idle at the prompt, or a command in a terminal session that produces output without further input, does not reset the idle clock. Type into the session or reconnect to reset it.

## Connect with VS Code [#connect-with-vs-code]

You can connect VS Code directly to your sandbox using the Remote SSH extension.

1. Install the [Remote Explorer extension ↗](https://marketplace.visualstudio.com/items?itemName=ms-vscode.remote-explorer)
2. Add a new SSH connection
3. When prompted for the SSH connection URL, paste the SSH command from above

For more information, see the [VS Code Remote SSH documentation ↗](https://code.visualstudio.com/docs/remote/ssh).

## Connect with JetBrains IDEs [#connect-with-jetbrains-ides]

JetBrains Gateway provides remote development support for connecting to your sandbox.

1. Download [JetBrains Gateway ↗](https://www.jetbrains.com/remote-development/gateway/)
2. Add a new connection
3. When prompted for the SSH connection URL, paste the SSH command from above
4. Select the IDE to install in your sandbox

## Security [#security]

SSH access is stateless, with no per-sandbox access list. A sandbox access token reaches only its sandbox and only the operations it allows, until it expires. A project API key reaches every sandbox in the project and the API, so prefer a token for SSH.

<Callout type="caution" title="Warning">
Anyone with the SSH command can access the sandbox while its credential is valid. Treat it like a secret. A sandbox access token stays valid until it expires and cannot be revoked. To cut off access through an API key, revoke or rotate the key on the [API keys](/docs/api-keys) page.
</Callout>

### Host keys [#host-keys]

The host key your client sees is the gateway's, not the sandbox's. Each region's gateway presents one stable ed25519 host key, so the known hosts file holds one entry per gateway host and stays valid across sandboxes and gateway restarts. A host key change warning for a gateway host you have connected to before should be treated as suspicious.

The gateway-to-sandbox hop runs inside the private overlay network. The in-sandbox SSH server is reachable only from the gateway, not from other sandboxes or the internet.

## Permissions [#permissions]

SSH access uses the sandbox permissions of the project, which you can grant to an [organization role](/docs/organizations#roles) or an [API key](/docs/api-keys).

A [sandbox access token](/docs/sandboxes#create-an-access-token) with the ssh operation grants SSH access to a single sandbox, without an API key.

| **Permission**        | **Grants**                                                     |
| --------------------- | -------------------------------------------------------------- |
| **`write:sandboxes`** | Connect over SSH with an API key, and create SSH access tokens |
