This guide provides instructions for using the provided Python script below to immediately and permanently remove a specific SSH public key from one or more servers managed by your RunCloud account.
When you add an SSH key to RunCloud, you can mark it as temporary, which will automatically remove it after 12 hours. This option is highly recommended for granting short-term access, as you won’t then need to manually remove the key afterward.
However, if a team member is leaving the team, or you suspect a private key has been compromised, you will want to revoke access and remove the corresponding public key immediately.
In this case, you can use the following script with the RunCloud API to remove a provided SSH key from all your servers.
Warning: This is a Destructive Action
This script will connect to each server in your Workspace. For each of those servers, it will fetch all SSH keys and, if a match is found, it will permanently delete that key for every single system user account (e.g., runcloud, root, and any other custom users) where that key has been installed. If you want to connect to the server again, you will need to add keys again on your own.
Prerequisites
Before you begin, ensure you have the following:
- Python 3 installed on your local machine.
- Your RunCloud API Bearer Token. You can generate one from your RunCloud dashboard under Account Settings → API.
- The full public SSH key string that you wish to delete (e.g.,
ssh-rsa AAAAB3... user@machine
).
Python Script to Remove SSH Keys
import http.client
import json
import sys
# --- Configuration ---
# IMPORTANT: Replace the placeholder values below with your actual data.
# The full public key you want to delete.
# Example: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQ..."
PUBLIC_KEY_TO_DELETE = "PLEASE_PASTE_THE_FULL_PUBLIC_KEY_HERE"
# --- Configuration ---
RUNCLOUD_API_HOST = "manage.runcloud.io"
# IMPORTANT: Replace with your actual Bearer Token from your RunCloud API settings.
auth_string = 'PLEASE_REPLACE_WITH_YOUR_RUNCLOUD_API_BEARER_TOKEN'
AUTH_HEADER = {
"Authorization": f"Bearer {auth_string}",
"Accept": "application/json",
"user-agent": "Python Script to Delete SSH Keys"
}
# Number of characters to use for matching the public key. 50 is a safe default.
KEY_MATCH_PREFIX_LENGTH = 50
# --- Helper Function for API Requests ---
def make_api_request(method, path, payload=None, extra_headers=None):
conn = None # Initialize conn to None
try:
conn = http.client.HTTPSConnection(RUNCLOUD_API_HOST)
# Prepare headers
headers = AUTH_HEADER.copy() # Start with base auth headers
if extra_headers:
headers.update(extra_headers)
# Ensure Content-Type is set for requests with a payload
if payload and 'Content-Type' not in headers:
headers['Content-Type'] = 'application/json'
# Prepare payload
body = json.dumps(payload) if payload else b''
conn.request(method, path, body=body, headers=headers)
res = conn.getresponse()
status = res.status
data = res.read()
response_body_str = data.decode("utf-8")
# Try to parse JSON, but return raw string if not JSON or if error
try:
response_json = json.loads(response_body_str)
return status, response_json
except json.JSONDecodeError:
print(f" Warning: Response was not valid JSON.")
print(f" Raw Response Body: {response_body_str[:200]}...") # Print start of body
if 200 <= status < 300:
return status, {"raw_response": response_body_str}
else:
return status, {"error": "Invalid JSON response", "details": response_body_str}
except http.client.HTTPException as e:
print(f"[Error] HTTP Exception during {method} {path}: {e}")
return None, {"error": "HTTP Exception", "details": str(e)}
except ConnectionError as e:
print(f"[Error] Connection Error during {method} {path}: {e}")
return None, {"error": "Connection Error", "details": str(e)}
except Exception as e:
print(f"[Error] Unexpected error during {method} {path}: {e}")
return None, {"error": "Unexpected Error", "details": str(e)}
finally:
if conn:
conn.close()
def find_and_delete_ssh_key(server_id: int, key_to_delete_full: str):
# 1. Fetch all existing SSH keys from the server
print(f" Fetching SSH keys...")
list_path = f"/api/v3/servers/{server_id}/ssh/credentials"
status, response = make_api_request("GET", list_path)
if not (200 <= status < 300 and "data" in response):
error_msg = response.get("message", "Unknown error")
print(f" [ERROR] Could not fetch SSH keys for server {server_id}.")
print(f" Status: {status}, Details: {error_msg}")
return
# Prepare the prefix for matching
key_prefix_to_find = key_to_delete_full.strip()[:KEY_MATCH_PREFIX_LENGTH]
# 2. Find the target key by comparing the prefix
for key_data in response["data"]:
api_key_prefix = key_data.get("publicKey", "").strip()[:KEY_MATCH_PREFIX_LENGTH]
if api_key_prefix == key_prefix_to_find:
print(f" -> Found matching key: '{key_data['label']}' (ID: {key_data['id']}).")
print(f" Deleting key...")
delete_path = f"/api/v3/servers/{server_id}/ssh/credentials/{key_data['id']}"
status, response = make_api_request("DELETE", delete_path)
if 200 <= status < 300:
print(f" [SUCCESS] Successfully deleted key '{key_data['label']}' for user account '{key_data['user']}' from server {server_id}.")
else:
error_msg = response.get("message", "Unknown error")
print(f" [ERROR] Failed to delete key '{key_data['label']}' from server {server_id}.")
print(f" Status: {status}, Details: {error_msg}")
def main():
# --- Configuration Validation ---
if "PLEASE_PASTE" in PUBLIC_KEY_TO_DELETE or not PUBLIC_KEY_TO_DELETE:
print(
"[FATAL] The `PUBLIC_KEY_TO_DELETE` variable is not set. "
"Please edit the script and paste the full public key."
)
sys.exit(1)
status, servers_response = make_api_request("GET", "/api/v3/servers")
if not (status and 200 <= status < 300):
print(f"[FATAL] Could not fetch servers. API responded with status {status}.")
print(f" Details: {servers_response.get('message', 'No message')}")
return
servers = servers_response.get('data', [])
if not servers:
print("No servers found in your RunCloud Workspace.")
return
print(f"Found {len(servers)} server(s). Starting process...")
# --- Process each server ---
for server in servers:
print(f"\n--- Processing Server: {server['name']} (ID: {server['id']}) ---")
find_and_delete_ssh_key(server['id'], PUBLIC_KEY_TO_DELETE)
print("\n--- Process finished ---")
main()
How to Use This Script
Step 1: Save the Script
Save the Python code provided above to a file on your computer. A suitable name would be delete_ssh_key.py
.
Step 2: Configure the Script
Open the delete_ssh_key.py file in a text editor and modify the configuration variables at the top of the script.
PUBLIC_KEY_TO_DELETE:
Replace the placeholder text with the complete public key string you want to remove.
PUBLIC_KEY_TO_DELETE = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCm+123...dev@runcloud"
auth_string:
Replace the placeholder token with your actual RunCloud API Bearer Token.
auth_string = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3b3Jrc3BhY2V...WU7360onlcg'
Save the file after making your changes.
Step 3: Run the Script
Open your terminal or command prompt, navigate to the directory where you saved the delete_ssh_key.py file, and execute it using the following command:
python3 delete_ssh_key.py
Step 4: Review the Output
The script will print its progress to the terminal.
- For each server, it will indicate that it is fetching the keys.
- If the target key is found, it will print a ‘Found matching key’ message.
- A [SUCCESS] message confirms that the key was successfully deleted from that server.

Step 5: Managing Keys in the RunCloud Key Vault
After deleting the keys from your servers using this script, you might also need to delete the key from the vault. The script removes the key’s deployment from the servers, but the key definition itself will still exist within your RunCloud account’s central repository of SSH credentials.
If you do not remove it from the vault, it could be accidentally redeployed to the same servers or new ones in the future.
To permanently remove the key definition from your account, navigate to Settings → SSH Key Vault in your RunCloud dashboard. From there, you can manage and delete any keys that are no longer needed.

Customizing the Script
The above script serves as an excellent starting point for more advanced automation. The RunCloud API is a powerful tool, and with a few modifications, you can tailor this script to a wide variety of administrative needs.
For example, it is a highly recommended security practice to disallow root login entirely on your server, which you can do via a setting in the RunCloud dashboard. However, you can also use the API to enforce security policies programmatically, such as removing all SSH keys assigned to the root user across your servers.
You can achieve this by slightly modifying the logic in the find_and_delete_ssh_key
function. Instead of matching the public key, you would iterate through the keys and check their associated user.