# SSH

**Page:** foundation/networking/ssh

[Download Raw Markdown](./foundation/networking/ssh.md)

---

# SSH

**SSH provides traditional command-line access to containers.** But on Hoody, SSH is **optional**—[hoody-terminal](/kit/terminals/) gives you web-based shell access without any SSH configuration.


**Hoody Terminal doubles as an HTTP-to-SSH bridge.** You can manage *any* remote server — not just Hoody containers — directly from your browser or via the Terminal API, without installing an SSH client on your device.

Add SSH parameters to any terminal URL or API call, and the Hoody container makes the SSH connection for you:

```
https://PROJECT-CONTAINER-terminal-1.SERVER.containers.hoody.icu/
  ?ssh_host=production-server.com
  &ssh_user=admin
  &ssh_password=...
```

Or programmatically (key-based auth):
```bash
# ssh_key is the base64-encoded private key CONTENT, not a file path
SSH_KEY=$(base64 -w0 ~/.ssh/prod.pem | jq -sRr @uri)
curl -X POST "https://...-terminal-1.../api/v1/terminal/execute?ssh_host=prod.example.com&ssh_user=admin&ssh_key=$SSH_KEY" \
  -H "Content-Type: application/json" \
  -d '{"command": "systemctl status nginx", "wait": true}'
```

**Parameters:** `ssh_host` (required), `ssh_user` (required), `ssh_port` (default: 22), `ssh_password` or `ssh_key`, `socks5_host`/`socks5_port` for proxy routing. Note: `ssh_key` is the **base64-encoded private key content**, not a file path—the container cannot read paths on your device.

The SSH connection persists across requests — subsequent commands to the same terminal session reuse the connection. Use different `terminal_id` values for simultaneous connections to multiple servers.

See [Terminals: SSH to Remote Servers](/kit/terminals/#6-ssh-to-remote-servers-no-client-needed) for full details, multi-server orchestration patterns, and practical workflows.


---

## API Endpoints Summary

**Official Technical Reference:**

SSH configuration is part of container creation/updates:

- **[POST /api/v1/projects/\{id\}/containers](/api/containers/)** - Create container with `ssh_public_key`
- **[PATCH /api/v1/containers/\{id\}](/api/containers/)** - Update `ssh_public_key` on existing container
- **[GET /api/v1/containers/\{id\}](/api/containers/)** - View current SSH configuration

**Alternative Access:**
- **[Hoody Terminal](/kit/terminals/)** - Web-based shell (no SSH needed)

---

## SSH vs hoody-terminal

**Important:** SSH is NOT required to access containers on Hoody.

| Feature | SSH | hoody-terminal |
|---------|-----|----------------|
| **Setup** | Generate keys, configure client | Just visit URL |
| **Access** | Desktop/mobile SSH clients | Any web browser |
| **Session** | Closes when disconnected | **Persists when you leave** |
| **Background Processes** | Must use screen/tmux | **Run directly, session maintained** |
| **Performance** | SSH protocol | **HTTP/2 & HTTP/3 (faster)** |
| **File Transfer** | SFTP, rsync, scp | Via hoody-files HTTP API |
| **Use Cases** | VS Code Remote, SFTP, local tools | Quick access, mobile, zero config |


**hoody-terminal advantage:** Close your browser, come back later—your session is exactly as you left it. Background processes keep running.


---

## How Hoody's SSH Proxy Works

**Hoody provides TWO ways to SSH into containers:**

### Method 1: Privacy-First Routing (Recommended)

```bash
ssh -i ~/.ssh/key root@ssh.$serverName.containers.hoody.icu
```

**Privacy benefit:** The endpoint is **the SAME for ALL your containers**. The SSH service:
- Doesn't reveal which container you're connecting to
- Routes purely by public key from SSH handshake
- Endpoint observers can't correlate domains to containers
- Your public key is your identity (not visible in connection URL)

**Example:** `ssh.us-west-1.containers.hoody.icu` handles ALL containers on that server.

### Method 2: Direct Container URL

```bash
ssh -i ~/.ssh/key root@{project}-{container}-ssh.{server}.containers.hoody.icu
# OR
ssh -i ~/.ssh/key root@{project}-{container}-ssh-22.{server}.containers.hoody.icu
```

**Trade-off:** Container identity visible in URL, but easier to script/automate when you need to target specific containers by name.

---

## SSH Routing Architecture

```
Your SSH Client
       ↓
ssh -i ~/.ssh/key root@ssh.$serverName.containers.hoody.icu
       ↓
Hoody SSH Proxy (same endpoint for ALL containers)
  ├─ Reads public key from SSH handshake
  ├─ Matches key to container (one-to-one mapping)
  └─ Routes connection to that specific container
       ↓
Container with matching public key
```

**Critical rule:** **Each container must have a UNIQUE public key.**


Same public key on 2 containers = routing conflict. The SSH Proxy identifies containers by their public key, so **never reuse keys**.


---

## Generating SSH Keys

**You must generate a NEW key pair for EACH container.**


**SDK helper:** The Hoody SDK ships a subpath helper `@hoody-ai/hoody-sdk/ssh-keys` exposing `generateEd25519SshKeyPair()`, `parseEd25519SshPublicKey()`, and `formatEd25519SshPublicKey()` for generating and parsing Ed25519 keys programmatically—no shelling out to `ssh-keygen`.



  
    ```bash
    # Generate key (ed25519 recommended)
    ssh-keygen -t ed25519 -f ~/.ssh/hoody-container-1 -C "container-1" -N ""
    
    # View public key
    cat ~/.ssh/hoody-container-1.pub
    # Copy the entire line starting with "ssh-ed25519..."
    ```
  
  
  
    **PowerShell:**
    ```powershell
    ssh-keygen -t ed25519 -f %USERPROFILE%\.ssh\hoody-container-1 -C "container-1" -N """"
    type %USERPROFILE%\.ssh\hoody-container-1.pub
    ```
    
    **PuTTY:** Use PuTTYgen → Generate → EdDSA/Ed25519 → Export OpenSSH key
  
  
  
    **Termux:**
    ```bash
    pkg install openssh
    ssh-keygen -t ed25519 -f ~/.ssh/hoody-container-mobile
    cat ~/.ssh/hoody-container-mobile.pub
    ```
    
    **JuiceSSH:** Identities → + → Generate → Ed25519 → Export Public Key
  
  
  
    **Termius:** Keychain → + → New Key → Ed25519 → Copy Public Key
    
    **Blink Shell:** Settings → Keys → + → Ed25519 → Copy public key text
  


---

## Adding SSH Key to Container

**During container creation:**


  
    ```bash
    # Create container with SSH key
    hoody containers create --project $PROJECT_ID \
      --server-id $SERVER_ID \
      --name "dev-container" \
      --dev-kit \
      --ssh-public-key "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMx... container-1"
    ```
  
  
    ```typescript
    const container = await client.api.containers.create(PROJECT_ID, {
      name: 'dev-container',
      server_id: SERVER_ID,
      hoody_kit: true,
      dev_kit: true,
      ssh_public_key: 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMx... container-1',
    });
    ```
  
  
    ```bash
    curl -X POST "https://api.hoody.icu/api/v1/projects/$PROJECT_ID/containers" \
      -H "Authorization: Bearer $TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
        "name": "dev-container",
        "server_id": "your_server_id",
        "hoody_kit": true,
        "dev_kit": true,
        "ssh_public_key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMx... container-1"
      }'
    ```
  


**Updating existing container:** Stop → Update `ssh_public_key` → Start

---

## Connecting via SSH

**Universal SSH connection (privacy-first):**

```bash
ssh -i ~/.ssh/hoody-container-1 root@ssh.us-west-1.containers.hoody.icu
```

**Or direct container URL:**

```bash
ssh -i ~/.ssh/hoody-container-1 root@myproject-dev-ssh.us-west-1.containers.hoody.icu
# OR with port in subdomain
ssh -i ~/.ssh/hoody-container-1 root@myproject-dev-ssh-22.us-west-1.containers.hoody.icu
```

**Privacy method:** Same endpoint for all containers. The SSH Proxy routes by your public key.

### Platform-Specific Clients


  
    ```bash
    # Privacy method (recommended)
    ssh -i ~/.ssh/hoody-container-1 root@ssh.us-west-1.containers.hoody.icu
    
    # Or direct URL
    ssh -i ~/.ssh/hoody-container-1 root@myproject-dev-ssh.us-west-1.containers.hoody.icu
    ```
    
    **~/.ssh/config:**
    ```
    Host dev-container
        HostName ssh.us-west-1.containers.hoody.icu
        User root
        IdentityFile ~/.ssh/hoody-container-1
    ```
    Then: `ssh dev-container`
  
  
  
    Same as Linux. Use Terminal.app or iTerm2.
    
    **VS Code:** Install Remote-SSH extension → Connect to `root@ssh.$serverName.containers.hoody.icu`
  
  
  
    **PowerShell:**
    ```powershell
    ssh -i %USERPROFILE%\.ssh\hoody-container-1 root@ssh.us-west-1.containers.hoody.icu
    ```
    
    **PuTTY:** Host: `ssh.us-west-1.containers.hoody.icu` → Auth → Private key: `.ppk` file
  
  
  
    **JuiceSSH:** Connections → + → Address: `ssh.$serverName.containers.hoody.icu` → Identity: (select your key)
  
  
  
    **Termius:** Hosts → + → Hostname: `ssh.$serverName.containers.hoody.icu` → Key: (select your key)
  


---

## SFTP Support

**SSH connections support SFTP automatically.** Same key, same routing, file transfer protocol.

### FileZilla Configuration


  
    1. **File → Site Manager → New Site**
    2. **Protocol:** SFTP - SSH File Transfer Protocol
    3. **Host:** `ssh.us-west-1.containers.hoody.icu` (replace with your server)
    4. **Port:** 22
    5. **Logon Type:** Key file
    6. **User:** root
    7. **Key file:** Browse to `~/.ssh/hoody-container-1` (private key)
    8. **Connect**
  
  
  
    If FileZilla can't find your key:
    1. Settings → Connection → SFTP → Add key file
    2. Press **Cmd+Shift+G** in file browser
    3. Type: `~/.ssh`
    4. Select your key → Open
  


**Other SFTP Clients:**
- **Cyberduck:** New Connection → SFTP → Server: `ssh.$serverName.containers.hoody.icu` → Private Key: (browse)
- **WinSCP:** New Site → SFTP → Host: `ssh.$serverName.containers.hoody.icu` → Advanced → Private key file
- **Command-line:** `sftp -i ~/.ssh/hoody-container-1 root@ssh.$serverName.containers.hoody.icu`

---

## Best Practices

### 1. Generate Unique Keys Per Container

```bash
# ✅ Correct: One key per container
ssh-keygen -t ed25519 -f ~/.ssh/hoody-container-1
ssh-keygen -t ed25519 -f ~/.ssh/hoody-container-2

# ❌ Wrong: Reusing same key = BROKEN ROUTING
```

### 2. Use ed25519 Key Type

```bash
# ✅ Modern, secure, fast
ssh-keygen -t ed25519 -f ~/.ssh/hoody-container-1

# ⚠️ Legacy (use only if ed25519 not supported)
ssh-keygen -t rsa -b 4096 -f ~/.ssh/hoody-container-1
```

### 3. Use ~/.ssh/config for Multiple Containers

```bash
# ~/.ssh/config
Host dev
    HostName ssh.us-west-1.containers.hoody.icu
    User root
    IdentityFile ~/.ssh/hoody-container-1

Host prod
    HostName ssh.us-west-1.containers.hoody.icu
    User root
    IdentityFile ~/.ssh/hoody-container-2
```

Now connect with: `ssh dev` or `ssh prod`

**Same hostname, different keys** - SSH Proxy routes by public key.

### 4. Protect Private Keys

```bash
# Ensure correct permissions
chmod 600 ~/.ssh/hoody-container-*

# Never share private keys
# Never commit to version control
# Store securely (password manager, encrypted disk)
```

### 5. Use hoody-terminal for Quick Access

Don't configure SSH just for occasional commands. Use terminal URL instead:
```
https://{project}-{container}-terminal-1.{server}.containers.hoody.icu
```

Save SSH setup for when you need SFTP, VS Code Remote, or rsync operations.

---

## Useful Questions

### Can I SSH to containers without configuring SSH keys?

No. SSH requires public key authentication. However, you don't NEED SSH—use [hoody-terminal](/kit/terminals/) web interface instead (zero configuration). SSH is optional on Hoody.

### What happens if I use the same SSH key on multiple containers?

The SSH Proxy won't know which container to route to (routing conflict). **Always use unique keys per container.**

### Do I need to configure SSH if I only use hoody-terminal?

No. SSH is completely optional. If you only access containers via web browser, you never need SSH keys.

### Can I connect to containers via SSH from inside another container?

Yes! From Container A: `ssh -i /path/to/key root@ssh.$serverName.containers.hoody.icu` routes to Container B (based on public key).

### Can I SSH to remote servers (not Hoody containers) through the terminal?

Yes. Hoody Terminal acts as an HTTP-to-SSH bridge. Add `ssh_host` and `ssh_user` parameters to any terminal URL or execute request, and the container establishes the SSH connection for you. No SSH client needed on your device. See the SSH Bridge callout at the top of this page, or [Terminals: SSH to Remote Servers](/kit/terminals/#6-ssh-to-remote-servers-no-client-needed) for full details.

### Does SSH work with containers in "block" network mode?

Yes. SSH connections are **INBOUND** (to container), while block mode prevents **OUTBOUND** (from container). Note: the SSH bridge (terminal connecting *out* to remote servers) requires outbound access.

### What user do I connect as?

`root` by default. Containers run as root user.

### Can I disable SSH and only use hoody-terminal?

Yes. Set `ssh_public_key: null` to clear the key (omitting the field instead inherits the project default, if one is set). With no key assigned, the container has no SSH access, but the hoody-terminal URL still works. If your project defines a default SSH key, clear that default to guarantee no container inherits one.

### Does FileZilla support both SFTP and hoody-files?

FileZilla supports SFTP (via SSH protocol). For hoody-files (HTTP-based), use a web browser. FileZilla is SSH-only.

---

## Troubleshooting

### SSH Connection Refused

**Problem:** `ssh` returns "Connection refused"

**Solutions:**
1. Verify container is running: `GET /api/v1/containers/{id}` → check `"status": "running"`
2. Test SSH proxy connectivity: `telnet ssh.$serverName.containers.hoody.icu 22`
3. Check key permissions: `chmod 600 ~/.ssh/hoody-container-1`

### SSH Key Not Recognized

**Problem:** "Permission denied (publickey)"

**Solutions:**
1. Verify correct key: `cat ~/.ssh/hoody-container-1.pub` matches container's `ssh_public_key`
2. Check key format: Must start with `ssh-ed25519`, `ssh-rsa`, or `ecdsa-sha2-nistp*`
3. Use verbose logging: `ssh -v -i ~/.ssh/hoody-container-1 root@ssh.$serverName.containers.hoody.icu`

### Multiple Containers with Same Key

**Problem:** SSH sometimes connects to wrong container

**Cause:** Duplicate public keys across containers

**Solution:** Generate unique keys for each container, update via API

### FileZilla Can't Find SSH Key

**Problem:** FileZilla says "No supported authentication methods available"

**Solutions:**
1. **Import key first:** FileZilla → Edit → Settings → Connection → SFTP → Add key file
2. **Use Interactive logon:** Logon Type: Interactive (FileZilla uses imported key automatically)
3. **On macOS:** Use Cmd+Shift+G → Type `~/.ssh` when browsing for key

### SSH Key Rotation

**Changing SSH key for a container:**

**Step 1:** Generate new key locally:
```bash
ssh-keygen -t ed25519 -f ~/.ssh/hoody-container-new -N ""
```

**Step 2:** Stop container, update key, start:


  
    ```bash
    # Stop container
    hoody containers manage $CONTAINER_ID stop

    # Update SSH public key
    hoody containers update $CONTAINER_ID \
      --ssh-public-key "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5... (new key)"

    # Start container
    hoody containers manage $CONTAINER_ID start
    ```
  
  
    ```typescript
    // Stop container
    await client.api.containers.manage(CONTAINER_ID, 'stop');

    // Update SSH public key
    await client.api.containers.update(CONTAINER_ID, {
      ssh_public_key: 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5... (new key)',
    });

    // Start container
    await client.api.containers.manage(CONTAINER_ID, 'start');
    ```
  
  
    ```bash
    # Stop container (single lifecycle route: POST /api/v1/containers/{id}/{operation})
    curl -X POST "https://api.hoody.icu/api/v1/containers/$CONTAINER_ID/stop" \
      -H "Authorization: Bearer $TOKEN"

    # Update SSH public key
    curl -X PATCH "https://api.hoody.icu/api/v1/containers/$CONTAINER_ID" \
      -H "Authorization: Bearer $TOKEN" \
      -H "Content-Type: application/json" \
      -d '{"ssh_public_key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5... (new key)"}'

    # Start container
    curl -X POST "https://api.hoody.icu/api/v1/containers/$CONTAINER_ID/start" \
      -H "Authorization: Bearer $TOKEN"

    # {operation} enum: start | stop | force-stop | restart | pause | resume
    ```
  


**Step 3:** Test with new key:
```bash
ssh -i ~/.ssh/hoody-container-new root@ssh.$serverName.containers.hoody.icu
```

---

## What's Next

**Complete your networking setup:**
- **[Firewall →](./firewall/)** - Control traffic at packet level
- **[Network Configuration →](./network/)** - Route through VPNs/proxies, change exit IP
- **[IPv4 Management →](./ipv4/)** - Dedicated IP addresses (coming soon)

**Alternative access methods:**
- **[Hoody Terminal →](/kit/terminals/)** - Web-based shell (no SSH needed)
- **[Hoody Files →](/kit/files/)** - HTTP file access (no SFTP needed)

**Understanding gained:**
- ✅ SSH is optional (hoody-terminal provides web alternative)
- ✅ Each container needs UNIQUE SSH public key
- ✅ Hoody SSH Proxy routes by public key
- ✅ SFTP works automatically (same key, same routing)
- ✅ FileZilla and all SFTP clients supported

---

> **SSH is traditional access to containers.**  
> **hoody-terminal is modern web access.**  
> **Each container = unique SSH key for proper routing.**

**SSH when you need local tools. hoody-terminal when you need zero setup.**