---
title: "SSH Access"
description: "SSH into Freestyle VMs with scoped identity tokens."
url: "/docs/vms/ssh"
---

Freestyle VMs accept SSH through `vm-ssh.freestyle.sh`. Access is controlled with Freestyle identities and tokens.


## SSH Format

```bash
ssh <vm-id>@vm-ssh.freestyle.sh
ssh <vm-id>:<access-token>@vm-ssh.freestyle.sh
ssh <vm-id>+<linux-user>:<access-token>@vm-ssh.freestyle.sh
```

If you omit the token from the SSH URL, SSH prompts for it as the password.


## Create A Token

```ts
import { freestyle } from "freestyle";

const { vm, vmId } = await freestyle.vms.create();

const { identity } = await freestyle.identities.create();
await identity.permissions.vms.grant({
  vmId,
});

const { token } = await identity.tokens.create();

console.log(`ssh ${vmId}:${token}@vm-ssh.freestyle.sh`);
```


## SSH As A Linux User

Linux users are normal guest OS users. Create them inside the VM, then grant an identity access to the matching username.

```ts
const { vm, vmId } = await freestyle.vms.create();

await vm.exec({
  command: `
set -e
if ! id -u developer >/dev/null 2>&1; then
  useradd --create-home --shell /bin/bash developer
fi
if getent group sudo >/dev/null 2>&1; then
  usermod --append --groups sudo developer
fi
mkdir -p /home/developer/workspace
chown -R developer:developer /home/developer
`,
});

const { identity } = await freestyle.identities.create();
await identity.permissions.vms.grant({
  vmId,
  allowedUsers: ["developer"],
});

const { token } = await identity.tokens.create();

console.log(`ssh ${vmId}+developer:${token}@vm-ssh.freestyle.sh`);
```

Freestyle base images configure `sshd` with an `AuthorizedKeysCommand` that reads the current Freestyle VM SSH public key from VM metadata. Any Linux account you create can use Freestyle SSH as long as that SSH configuration remains in place and the account has a valid login shell.

For custom images or heavily modified SSH configs, keep this behavior enabled:

```bash
grep -R "AuthorizedKeysCommand /usr/local/bin/fetch-ssh-keys" /etc/ssh/sshd_config /etc/ssh/sshd_config.d
test -x /usr/local/bin/fetch-ssh-keys
```

You do not need to copy Freestyle access tokens into the VM. Tokens stay outside the VM and are checked by the SSH proxy before it connects to the Linux account.


## Multiple Developers

Create separate identities when different users or agents should have different VM permissions.

```ts
const { identity: alice } = await freestyle.identities.create();
await alice.permissions.vms.grant({
  vmId,
  allowedUsers: ["alice"],
});

const { identity: bob } = await freestyle.identities.create();
await bob.permissions.vms.grant({
  vmId,
  allowedUsers: ["bob"],
});
```

Keep your Freestyle API key server-side. Send only scoped access tokens to clients or developers.


## Editor Connections

Editor connections use the same Freestyle SSH proxy and scoped access tokens as command-line SSH. Create an identity, grant it access to the VM, mint a token, then pass that token in the editor connection URL.

For VS Code and Cursor use one of these URL formats:

```text
vscode://vscode-remote/ssh-remote+<vm-id>,<access-token>@<vm-id>.vm-ssh.freestyle.sh?windowId=_blank
cursor://vscode-remote/ssh-remote+<vm-id>,<access-token>@<vm-id>.vm-ssh.freestyle.sh?windowId=_blank
```

For cmux, use its SSH URL format:

```text
cmux://ssh?host=<vm-id>.vm-ssh.freestyle.sh&user=<vm-id>,<access-token>&name=<name>
```
