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

# SSH key management for SFTP storage

export const MethodSection = ({children}) => children ?? null;

export const MethodSwitch = ({children}) => {
  const tabs = React.Children.toArray(children).map(c => {
    if (!c || !c.props) return null;
    if (c.props.id) return c;
    const inner = c.props.children;
    if (inner && inner.props && inner.props.id) return inner;
    return null;
  }).filter(Boolean);
  const firstId = tabs.length > 0 ? tabs[0].props.id : "";
  const [active, setActive] = React.useState(firstId);
  React.useEffect(() => {
    try {
      const saved = localStorage.getItem("gcore_docs_method");
      if (saved && tabs.find(t => t.props.id === saved)) {
        setActive(saved);
      }
    } catch (_) {}
  }, []);
  React.useEffect(() => {
    try {
      document.querySelectorAll("h2[id], h3[id]").forEach(heading => {
        const visible = heading.offsetParent !== null;
        document.querySelectorAll(`a[href="#${heading.id}"]`).forEach(link => {
          if (link.closest("h1,h2,h3,h4,h5,h6")) return;
          const li = link.closest("li");
          if (li) li.style.display = visible ? "" : "none";
        });
      });
    } catch (_) {}
    window.dispatchEvent(new Event("scroll"));
  }, [active]);
  const handleClick = id => {
    setActive(id);
    try {
      localStorage.setItem("gcore_docs_method", id);
    } catch (_) {}
  };
  return <div>
      <div className="not-prose flex gap-0 border-b border-zinc-200 dark:border-zinc-800 mb-8 mt-2" role="tablist">
        {tabs.map(tab => {
    const isActive = active === tab.props.id;
    return <button key={tab.props.id} role="tab" aria-selected={isActive} onClick={() => handleClick(tab.props.id)} className={["px-4 py-2 text-sm font-medium border-b-2 -mb-px transition-colors cursor-pointer", isActive ? "border-primary text-primary" : "border-transparent text-zinc-500 hover:text-zinc-800 dark:hover:text-zinc-200"].join(" ")}>
              {tab.props.label}
            </button>;
  })}
      </div>

      {tabs.map(tab => <div key={tab.props.id} style={{
    display: active === tab.props.id ? "" : "none"
  }}>
          {tab.props.children}
        </div>)}
    </div>;
};

<MethodSwitch>
  <MethodSection id="portal" label="Customer Portal">
    <p>SSH keys provide passwordless authentication for SFTP storage. Keys are created and managed in the [Gcore Customer Portal](https://portal.gcore.com).</p>

    ## Adding an SSH key

    <Steps>
      <Step title="Open the SFTP SSH Keys section">
        In the Gcore Customer Portal, navigate to **Object Storage** > **SFTP SSH Keys**:

        <Frame>
          <img src="https://mintcdn.com/gcore/bP_M3iNvkCNJWoX1/images/docs/storage/manage-sftp-storage/create-and-add-an-ssh-key-to-your-storage/ssh-keys-list.png?fit=max&auto=format&n=bP_M3iNvkCNJWoX1&q=85&s=deb63417216807d091ee44455e214471" alt="SFTP SSH Keys page with the Add new key button" width="1085" height="429" data-path="images/docs/storage/manage-sftp-storage/create-and-add-an-ssh-key-to-your-storage/ssh-keys-list.png" />
        </Frame>
      </Step>

      <Step title="Add a new key">
        Click **Add new key**. The form opens:

        <Frame>
          <img src="https://mintcdn.com/gcore/bP_M3iNvkCNJWoX1/images/docs/storage/manage-sftp-storage/create-and-add-an-ssh-key-to-your-storage/add-new-key-form.png?fit=max&auto=format&n=bP_M3iNvkCNJWoX1&q=85&s=db7fbb089747ba28fd9a901c3c3b09b7" alt="Add new key dialog with Name and Key fields" width="810" height="747" data-path="images/docs/storage/manage-sftp-storage/create-and-add-an-ssh-key-to-your-storage/add-new-key-form.png" />
        </Frame>
      </Step>

      <Step title="Name the key">
        Enter a name using letters, numbers, underscores, and dashes. The name cannot exceed 128 characters.
      </Step>

      <Step title="Add the public key and save">
        Paste the public key into the **Key** field, or click **Upload from file** to load it from a file. Click **Add** to save.
      </Step>

      <Step title="Assign the key to SFTP storage">
        Navigate to **Object Storage** > **Object Storages**. Find the SFTP storage, open its three-dot menu, and select **SSH keys manager**:

        <Frame>
          <img src="https://mintcdn.com/gcore/bP_M3iNvkCNJWoX1/images/docs/storage/manage-sftp-storage/create-and-add-an-ssh-key-to-your-storage/sftp-storage-three-dot-menu.png?fit=max&auto=format&n=bP_M3iNvkCNJWoX1&q=85&s=59d1ed3bbd0187dfc1ed9a8fc160654d" alt="Object Storages list with the three-dot menu open showing SSH keys manager option" width="1440" height="695" data-path="images/docs/storage/manage-sftp-storage/create-and-add-an-ssh-key-to-your-storage/sftp-storage-three-dot-menu.png" />
        </Frame>

        In the dialog that opens, click **Add new SSH key**, enter the key name and paste the public key, then click **Add**:

        <Frame>
          <img src="https://mintcdn.com/gcore/bP_M3iNvkCNJWoX1/images/docs/storage/manage-sftp-storage/create-and-add-an-ssh-key-to-your-storage/ssh-keys-manager-dialog.png?fit=max&auto=format&n=bP_M3iNvkCNJWoX1&q=85&s=18b6b2c599e50f27a8d2e0371824a5ec" alt="SSH keys manager dialog with Add new SSH key button" width="833" height="752" data-path="images/docs/storage/manage-sftp-storage/create-and-add-an-ssh-key-to-your-storage/ssh-keys-manager-dialog.png" />
        </Frame>
      </Step>
    </Steps>
  </MethodSection>

  <MethodSection id="api" label="REST API">
    <p>SSH key management uses two separate endpoints — one for the key registry and one for assigning keys to SFTP storage. Keys are stored account-wide and can be assigned to multiple storages. Up to five keys can be active per storage at the same time.</p>

    <Info>
      An [API token](/account-settings/api-tokens) is required.
    </Info>

    ```bash theme={null}
    export GCORE_API_KEY="{YOUR_API_KEY}"
    ```

    ## Add an SSH key

    <p>Creates a named SSH public key in the account-level key registry. Supported formats are `ssh-rsa` and `ssh-ed25519`.</p>

    | Parameter    | Required | Description                                                      |
    | ------------ | -------- | ---------------------------------------------------------------- |
    | `name`       | Yes      | Key name, letters/numbers/underscores/dashes, max 128 characters |
    | `public_key` | Yes      | Full SSH public key string including key type prefix             |

    <Tabs>
      <Tab title="Python SDK">
        ```python theme={null}
        from gcore import Gcore

        client = Gcore()

        key = client.storage.ssh_keys.create(
            name="my-ssh-key",
            public_key="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... user@host",
        )
        print(f"Key ID: {key.id}  name={key.name}")
        ```
      </Tab>

      <Tab title="Go SDK">
        ```go theme={null}
        package main

        import (
            "context"
            "fmt"
            "log"

            gcore "github.com/G-Core/gcore-go"
            "github.com/G-Core/gcore-go/storage"
        )

        func main() {
            client := gcore.NewClient()
            ctx := context.Background()

            key, err := client.Storage.SSHKeys.New(ctx, storage.SSHKeyNewParams{
                Name:      "my-ssh-key",
                PublicKey: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... user@host",
            })
            if err != nil {
                log.Fatalf("create ssh key: %v", err)
            }
            fmt.Printf("Key ID: %d  name=%s\n", key.ID, key.Name)
        }
        ```
      </Tab>

      <Tab title="curl">
        ```bash theme={null}
        curl -X POST https://api.gcore.com/storage/v4/ssh_keys \
          -H "Authorization: APIKey $GCORE_API_KEY" \
          -H "Content-Type: application/json" \
          -d '{
            "name": "my-ssh-key",
            "public_key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... user@host"
          }'
        ```

        The API returns:

        ```json theme={null}
        {
          "id": 2714,
          "name": "my-ssh-key",
          "public_key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... user@host",
          "created_at": "2026-07-13T16:41:35Z"
        }
        ```

        Save the key ID:

        ```bash theme={null}
        export SSH_KEY_ID=2714
        ```
      </Tab>
    </Tabs>

    ## List SSH keys

    <p>Returns all SSH keys stored in the account.</p>

    <Tabs>
      <Tab title="Python SDK">
        ```python theme={null}
        from gcore import Gcore

        client = Gcore()

        keys = client.storage.ssh_keys.list()
        for k in keys:
            print(f"id={k.id}  name={k.name}  created_at={k.created_at}")
        ```
      </Tab>

      <Tab title="Go SDK">
        ```go theme={null}
        package main

        import (
            "context"
            "fmt"
            "log"

            gcore "github.com/G-Core/gcore-go"
            "github.com/G-Core/gcore-go/storage"
        )

        func main() {
            client := gcore.NewClient()
            ctx := context.Background()

            page, err := client.Storage.SSHKeys.List(ctx, storage.SSHKeyListParams{})
            if err != nil {
                log.Fatalf("list ssh keys: %v", err)
            }
            for _, k := range page.Results {
                fmt.Printf("id=%d  name=%s\n", k.ID, k.Name)
            }
        }
        ```
      </Tab>

      <Tab title="curl">
        ```bash theme={null}
        curl https://api.gcore.com/storage/v4/ssh_keys \
          -H "Authorization: APIKey $GCORE_API_KEY"
        ```

        The API returns:

        ```json theme={null}
        {
          "count": 1,
          "results": [
            {
              "id": 2714,
              "name": "my-ssh-key",
              "public_key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... user@host",
              "created_at": "2026-07-13T16:41:35Z"
            }
          ]
        }
        ```
      </Tab>
    </Tabs>

    ## Assign keys to SFTP storage

    <p>Links one or more SSH keys to an SFTP storage by ID. The `ssh_key_ids` field replaces the entire list of assigned keys — include all keys that should remain active, not the new ones alone. Send an empty array to remove all keys.</p>

    | Parameter     | Required | Description                                                        |
    | ------------- | -------- | ------------------------------------------------------------------ |
    | `ssh_key_ids` | Yes      | Array of SSH key IDs to assign. Replaces all existing assignments. |

    <Tabs>
      <Tab title="Python SDK">
        ```python theme={null}
        import os
        from gcore import Gcore

        client = Gcore()
        storage_id = int(os.environ["SFTP_STORAGE_ID"])
        ssh_key_id = int(os.environ["SSH_KEY_ID"])

        sftp = client.storage.sftp_storages.update(storage_id, ssh_key_ids=[ssh_key_id])
        print(f"Assigned key IDs: {sftp.ssh_key_ids}")
        ```
      </Tab>

      <Tab title="Go SDK">
        ```go theme={null}
        package main

        import (
            "context"
            "fmt"
            "log"
            "os"
            "strconv"

            gcore "github.com/G-Core/gcore-go"
            "github.com/G-Core/gcore-go/storage"
        )

        func main() {
            sftpStorageID, _ := strconv.ParseInt(os.Getenv("SFTP_STORAGE_ID"), 10, 64)
            sshKeyID, _ := strconv.ParseInt(os.Getenv("SSH_KEY_ID"), 10, 64)

            client := gcore.NewClient()
            ctx := context.Background()

            sftp, err := client.Storage.SftpStorages.Update(ctx, sftpStorageID, storage.SftpStorageUpdateParams{
                SSHKeyIDs: []int64{sshKeyID},
            })
            if err != nil {
                log.Fatalf("assign keys: %v", err)
            }
            fmt.Printf("Assigned key IDs: %v\n", sftp.SSHKeyIDs)
        }
        ```
      </Tab>

      <Tab title="curl">
        ```bash theme={null}
        curl -X PATCH "https://api.gcore.com/storage/v4/sftp_storages/$SFTP_STORAGE_ID" \
          -H "Authorization: APIKey $GCORE_API_KEY" \
          -H "Content-Type: application/json" \
          -d '{"ssh_key_ids": [2714]}'
        ```

        The API returns the updated SFTP storage object with `ssh_key_ids` reflecting the new assignment.
      </Tab>
    </Tabs>

    ## Delete an SSH key

    <p>Removes an SSH key from the account registry. If the key is currently assigned to any SFTP storage, it is also removed from those storages automatically.</p>

    <Tabs>
      <Tab title="Python SDK">
        ```python theme={null}
        import os
        from gcore import Gcore

        client = Gcore()
        ssh_key_id = int(os.environ["SSH_KEY_ID"])

        client.storage.ssh_keys.delete(ssh_key_id)
        print("SSH key deleted")
        ```
      </Tab>

      <Tab title="Go SDK">
        ```go theme={null}
        package main

        import (
            "context"
            "fmt"
            "log"
            "os"
            "strconv"

            gcore "github.com/G-Core/gcore-go"
        )

        func main() {
            sshKeyID, _ := strconv.ParseInt(os.Getenv("SSH_KEY_ID"), 10, 64)

            client := gcore.NewClient()
            ctx := context.Background()

            err := client.Storage.SSHKeys.Delete(ctx, sshKeyID)
            if err != nil {
                log.Fatalf("delete ssh key: %v", err)
            }
            fmt.Println("SSH key deleted")
        }
        ```
      </Tab>

      <Tab title="curl">
        ```bash theme={null}
        curl -X DELETE "https://api.gcore.com/storage/v4/ssh_keys/$SSH_KEY_ID" \
          -H "Authorization: APIKey $GCORE_API_KEY"
        ```

        Returns HTTP 204 with no response body.
      </Tab>
    </Tabs>
  </MethodSection>

  <MethodSection id="terraform" label="Terraform">
    <p>Declare SSH keys for SFTP storage authentication using the Gcore [Terraform provider](/developer-tools/terraform/overview) v2.</p>

    ## Add an SSH key

    <p>Registers a public key in the account-level registry. Supported formats are `ssh-rsa` and `ssh-ed25519`. Changing the key name recreates the resource — the old key is deleted and a new one is created in its place.</p>

    ```hcl theme={null}
    resource "gcore_storage_ssh_key" "example" {
      name       = "my-ssh-key"
      public_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... user@host"
    }

    output "ssh_key_id" {
      value = gcore_storage_ssh_key.example.id
    }

    # terraform import gcore_storage_ssh_key.example '<ssh_key_id>'
    ```

    ```bash theme={null}
    terraform apply
    ```

    ## Assign keys to SFTP storage

    <p>Pass key IDs in the `ssh_key_ids` attribute of a `gcore_storage_sftp` resource. The list replaces all existing assignments — include every key that should remain active. Up to five keys are allowed per storage.</p>

    ```hcl theme={null}
    resource "gcore_storage_sftp" "example" {
      location_name = "ams"
      name          = "my-sftp-storage"
      password_mode = "none"
      ssh_key_ids   = [gcore_storage_ssh_key.example.id]
    }
    ```

    ```bash theme={null}
    terraform apply
    ```

    <p>To remove all key assignments, set `ssh_key_ids = []` and apply.</p>

    ## Delete an SSH key

    <p>Remove the resource block — Terraform deletes the key from the account registry on the next apply. Keys assigned to any SFTP storage are automatically unlinked before deletion.</p>

    ```hcl theme={null}
    # Remove or comment out this block:
    # resource "gcore_storage_ssh_key" "example" {
    #   name       = "my-ssh-key"
    #   public_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... user@host"
    # }
    ```

    ```bash theme={null}
    terraform apply
    ```
  </MethodSection>
</MethodSwitch>
