> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ns.rocks/llms.txt
> Use this file to discover all available pages before exploring further.

# SSH Access

> Connect to a machine with OpenSSH through certificate-backed relay access.

The default SSH flow is certificate-backed relay access:

```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
nullspace ssh mch_123
```

The CLI creates or reuses a local key under `~/.nullspace/ssh/`, asks the API
for a short-lived OpenSSH user certificate for that public key, writes the
certificate locally, and runs your local `ssh` client against the Nullspace SSH
relay. API credentials can mint access, but relay login still requires private
key proof of possession. When the API returns relay host-key metadata, the CLI
writes a pinned `known_hosts` entry under `~/.nullspace/ssh/` and asks OpenSSH
to verify that host key.

```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
nullspace ssh mch_123 -- python --version
nullspace machine ssh-access mch_123 --print-command
nullspace machine ssh-access mch_123 --json
```

<Warning>
  Treat SSH certificates, access IDs, and generated commands as sensitive. They
  expire quickly, but they should not be pasted into logs, issue trackers, or
  browser-visible application state.
</Warning>

## When to use SSH

Use SSH when a tool expects a real OpenSSH transport, when you want an
interactive shell outside the SDK, or when you need multiplexed SSH channels.
For programmatic commands and file transfer, prefer `machine.commands`, the
[Files API](../filesystem), and upload/download URLs. The relay does not expose
password login or SFTP.

## Quickstart

<Steps>
  <Step title="Install the CLI and OpenSSH">
    ```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
    python -m pip install "nullspace-sdk[cli]==1.0.0"
    ssh -V
    ```

    The CLI handles local key creation, certificate storage, and host-key
    pinning. You do not need `websocat` for the default relay flow.
  </Step>

  <Step title="Create a machine">
    ```python theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
    from nullspace import Machine

    machine = Machine.create(template="base", timeout=600)
    print(machine.id)
    ```

    Certificate-backed SSH access works with the normal machine runtime. You do
    not need to start an OpenSSH daemon inside the machine.
  </Step>

  <Step title="Connect with OpenSSH">
    ```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
    nullspace ssh mch_123
    nullspace ssh mch_123 -- python --version
    ```

    Remote command exit codes propagate through the local `ssh` process, so
    shell scripts can treat the command like any other SSH target.
  </Step>

  <Step title="Inspect the generated command">
    ```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
    nullspace machine ssh-access mch_123 --print-command
    nullspace machine ssh-access mch_123 --json
    ```

    Use this when another tool needs the exact `ssh` command. The command
    includes a short-lived certificate path and relay endpoint.
  </Step>
</Steps>

## Mint access for a public key

Use the SDK, CLI, or HTTP API when application code needs to mint access for a
caller-supplied public key. The TypeScript SDK does not expose an SSH-access
helper.

<CodeGroup>
  ```python Python SDK theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  from pathlib import Path
  from nullspace import Machine

  machine = Machine.connect("mch_123")
  access = machine.create_ssh_access(
      public_key=Path("~/.ssh/id_ed25519.pub").expanduser().read_text(),
      expires_in_minutes=10,
  )
  print(access.command)
  ```

  ```bash CLI theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  nullspace machine ssh-access mch_123 \
    --public-key @~/.ssh/id_ed25519.pub \
    --ttl 10m \
    --print-command
  ```

  ```bash HTTP API theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  curl -X POST "${NULLSPACE_API_URL}/v1/machines/mch_123/ssh-access" \
    -H "Authorization: Bearer ${NULLSPACE_API_KEY}" \
    -H "Content-Type: application/json" \
    -d '{
      "public_key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... user@example",
      "expires_in_minutes": 10
    }'
  ```
</CodeGroup>

## Supported behavior

| Capability                              | Status                                       |
| --------------------------------------- | -------------------------------------------- |
| Public-key proof of possession          | Supported                                    |
| Short-lived OpenSSH user certificates   | Supported                                    |
| Relay host-key pinning                  | Supported when host-key metadata is returned |
| Interactive shells                      | Supported                                    |
| SSH exec requests                       | Supported                                    |
| PTY allocation and resize               | Supported                                    |
| Multiple channels on one SSH connection | Supported                                    |
| Password authentication                 | Not supported                                |
| SFTP and SSH subsystems                 | Not supported                                |

## Troubleshooting

| Symptom                                 | Check                                                                                                                     |
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `nullspace ssh` is missing              | Install the CLI extra with `python -m pip install "nullspace-sdk[cli]==1.0.0"`.                                           |
| `Permission denied (publickey)`         | Re-run `nullspace ssh` so the CLI refreshes the certificate, and make sure the matching private key is available locally. |
| `Certificate invalid` or expired access | Mint a fresh certificate; SSH access is intentionally short-lived.                                                        |
| Host-key verification fails             | Remove stale relay entries from the CLI-managed `known_hosts` file shown in the generated command, then retry.            |
| File copy fails through SFTP            | Use `machine.files.upload`, `machine.files.download_url`, or volume file APIs.                                            |
| Relay connection is refused             | Confirm the machine is running and that the deployment has SSH relay enabled.                                             |

## Legacy signed WebSocket fallback

Older deployments may expose SSH through a signed public port-22
`websocket_url` with `websocat` in the OpenSSH `ProxyCommand`. That URL is a
bearer credential and is not the default product SSH flow. Prefer
`nullspace ssh` whenever the relay is available.

## Related

* [Preview URLs](../networking/preview-urls)
* [Access control](../networking/access-control)
* [PTY sessions](./pty)
* [Filesystem](../filesystem)
