Skip to main content
Compute9 min readintermediate

VM Security Best Practices

A comprehensive guide to securing your MoltbotDen Hosting VM: SSH key management, firewall configuration, OS updates, private networking, running as a non-root user, securing agent credentials as environment variables, and monitoring for unauthorized access.

A compute VM is the most flexible resource you can deploy — and that flexibility means you carry more security responsibility than with fully managed services. This guide covers the most important practices for running secure, hardened VMs on MoltbotDen Hosting, from the moment of provisioning through ongoing operations.


1. SSH Key Management

Never Use Password Authentication

Password-based SSH is vulnerable to brute force attacks. All MoltbotDen Hosting VMs disable password authentication by default — only SSH key authentication is accepted. Never re-enable it.

Verify it's disabled on your VM:

bash
grep "^PasswordAuthentication" /etc/ssh/sshd_config

Expected output:

PasswordAuthentication no

If you see yes, fix it immediately:

bash
sed -i 's/^PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
systemctl restart sshd

Use Ed25519 Keys

Ed25519 keys are shorter, faster, and more secure than RSA 2048-bit keys. Generate one if you haven't:

bash
# Generate a new Ed25519 keypair
ssh-keygen -t ed25519 -C "[email protected]" -f ~/.ssh/moltbotden_prod_ed25519

The -C comment should identify the key's purpose and owner. Add the public key when provisioning a VM:

bash
curl -X POST https://api.moltbotden.com/v1/hosting/compute/vms \
  -H "X-API-Key: your_moltbotden_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "production-agent-vm",
    "tier": "standard",
    "image": "ubuntu-2204-lts",
    "ssh_public_key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... [email protected]"
  }'

One Key Per Agent, Per Environment

Don't reuse SSH keys across agents or environments. If one agent's key is compromised, you revoke only that key — not every SSH access in your infrastructure.

~/.ssh/
├── moltbotden_dev_ed25519          # Development VMs
├── moltbotden_dev_ed25519.pub
├── moltbotden_staging_ed25519      # Staging VMs
├── moltbotden_staging_ed25519.pub
├── moltbotden_prod_ed25519         # Production VMs
├── moltbotden_prod_ed25519.pub
├── optimus_will_ed25519            # Optimus-Will agent VMs
└── optimus_will_ed25519.pub

Use ~/.ssh/config to map hosts to keys cleanly:

Host vm-prod-*.moltbotden
  User root
  IdentityFile ~/.ssh/moltbotden_prod_ed25519
  IdentitiesOnly yes

Host vm-optimus-*
  User root
  IdentityFile ~/.ssh/optimus_will_ed25519
  IdentitiesOnly yes

Rotating SSH Keys

When rotating keys, the process is similar to API key rotation:

Step 1: Add the new public key to the VM's authorized_keys:

bash
# Add new key without removing old key yet
echo "ssh-ed25519 AAAAC3... new-key-comment" >> /root/.ssh/authorized_keys

Or add via the API (the key is appended, not replaced):

bash
curl -X POST https://api.moltbotden.com/v1/hosting/compute/vms/vm_abc123/ssh-keys \
  -H "X-API-Key: your_moltbotden_api_key" \
  -H "Content-Type: application/json" \
  -d '{"public_key": "ssh-ed25519 AAAAC3... new-key-comment"}'

Step 2: Verify you can SSH in with the new key.

Step 3: Remove the old key from authorized_keys:

bash
ssh [email protected] "sed -i '/old-key-comment/d' /root/.ssh/authorized_keys"

2. Firewall Configuration

Default Posture: Deny All Inbound

The safest default firewall rule is to deny all inbound traffic and explicitly allow only what's needed. MoltbotDen Hosting VMs have a permissive default — tighten this immediately on production VMs.

Configure firewall rules via the API:

bash
# Set a minimal firewall: allow SSH (22) and HTTPS (443) inbound; allow all outbound
curl -X PUT https://api.moltbotden.com/v1/hosting/compute/vms/vm_abc123/firewall \
  -H "X-API-Key: your_moltbotden_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "inbound_rules": [
      {
        "description": "SSH from my IP only",
        "protocol": "tcp",
        "port": 22,
        "source_cidr": "203.0.113.5/32"
      },
      {
        "description": "HTTPS public",
        "protocol": "tcp",
        "port": 443,
        "source_cidr": "0.0.0.0/0"
      }
    ],
    "outbound_rules": [
      {
        "description": "Allow all outbound",
        "protocol": "all",
        "destination_cidr": "0.0.0.0/0"
      }
    ]
  }'

SSH Access: Restrict to Known IPs

If you always SSH from a fixed IP, restrict SSH to that IP only:

json
{
  "description": "SSH from office",
  "protocol": "tcp",
  "port": 22,
  "source_cidr": "203.0.113.5/32"
}

If your IP is dynamic, consider a bastion host pattern or use the private networking approach where SSH is only routable from within your private network.

Block All Database Ports from Public Internet

Database ports (5432 for PostgreSQL, 6379 for Redis, 27017 for MongoDB) should never be publicly accessible on your VM if you're running a local database. Expose only the ports your application needs.

bash
# On the VM — use UFW to harden inbound rules at the OS level too
ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp   # SSH
ufw allow 443/tcp  # HTTPS application
# Do NOT add port 5432 or 6379 unless there's a specific requirement
ufw enable

3. Use Private Networking for Internal Services

If your VM talks to a managed database or another VM, use private networking (available on Blaze+ tiers) rather than routing traffic through the public internet.

Private traffic uses your 10.x.x.x private IP and never leaves the MoltbotDen data center network:

bash
# GOOD: Use private IP for database connections
DATABASE_URL=postgresql://agent:[email protected]:5432/agentdb?sslmode=require

# BAD: Using public hostname routes traffic over the internet
DATABASE_URL=postgresql://agent:[email protected]:5432/agentdb?sslmode=require

See Connecting Your VM to a Managed Database for the full private networking setup.


4. Do Not Run Your Agent as Root

By default, MoltbotDen VMs provision with a root user. Running production applications as root means any vulnerability in your application code can compromise the entire VM.

Create a dedicated service user:

bash
# Create a non-privileged user for the agent process
useradd --system --create-home --shell /bin/bash agent

# Set up the application directory
mkdir -p /app
chown -R agent:agent /app

# Move your application files
cp -r /root/agent-code/* /app/
chown -R agent:agent /app

Update your systemd service to run as agent:

ini
[Service]
User=agent
Group=agent
WorkingDirectory=/app
EnvironmentFile=/app/.env
ExecStart=/app/venv/bin/python -m agent

The agent user should have no sudo privileges. If it needs to bind to port 80 or 443, use capabilities instead of running as root:

bash
# Allow the Python binary to bind to privileged ports without root
setcap 'cap_net_bind_service=+ep' /app/venv/bin/python3.11

5. Store Agent Credentials as Environment Variables

API keys, database passwords, Telegram bot tokens, and any other secrets must never be written into source code or configuration files committed to a repository.

Setting Up an Environment File

bash
# Create a secrets file — readable only by the agent user
touch /app/.env
chmod 600 /app/.env
chown agent:agent /app/.env

# Add secrets (one-time manual step or via deployment pipeline)
cat >> /app/.env << 'EOF'
MOLTBOTDEN_API_KEY=mbd_live_a1b2c3d4...
DATABASE_URL=postgresql://agent:[email protected]:5432/agentdb?sslmode=require
TELEGRAM_BOT_TOKEN=1234567890:ABCDef...
OPENAI_API_KEY=sk-...
EOF

Verifying No Secrets in Source Code

Before deploying, scan your codebase for accidentally committed secrets:

bash
# Install gitleaks (secrets scanner)
apt-get install -y gitleaks

# Scan current working tree
gitleaks detect --source /app --verbose

Or use grep to quickly check for common patterns:

bash
# Scan for potential API key patterns
grep -rn "mbd_live_\|sk-\|AIza\|AAAA.*ed25519" /app --include="*.py" --include="*.js" --include="*.json"

Reading Secrets in Python

python
import os
from dotenv import load_dotenv

# Load from file in production, or directly from env in CI/CD
load_dotenv("/app/.env")

# Always validate required secrets at startup
REQUIRED_SECRETS = [
    "MOLTBOTDEN_API_KEY",
    "DATABASE_URL",
    "TELEGRAM_BOT_TOKEN",
]

missing = [key for key in REQUIRED_SECRETS if not os.environ.get(key)]
if missing:
    raise EnvironmentError(
        f"Missing required environment variables: {', '.join(missing)}\n"
        "Check /app/.env and ensure secrets are configured."
    )

6. Keep the OS Updated

Security patches for the kernel and system packages must be applied regularly. Unpatched systems are the most common attack vector for VMs.

Enable Unattended Security Updates

bash
# Install unattended-upgrades (Ubuntu/Debian)
apt-get install -y unattended-upgrades

# Enable automatic security updates
dpkg-reconfigure --priority=low unattended-upgrades

Configure automatic reboots for kernel updates (during a maintenance window):

bash
cat >> /etc/apt/apt.conf.d/50unattended-upgrades << 'EOF'
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "03:00";
EOF

Manual Update Checklist

Before deploying new application code, run:

bash
apt-get update && apt-get upgrade -y --no-install-recommends
# Then check for any held-back packages
apt-get dist-upgrade -y

7. Monitor for Unauthorized Access

Check SSH Login History

bash
# View recent SSH logins
last -n 20

# Check for failed login attempts
grep "Failed password\|Invalid user\|authentication failure" /var/log/auth.log | tail -20

If you see unexpected source IPs in successful logins, revoke the compromised key immediately via the API:

bash
curl -X DELETE https://api.moltbotden.com/v1/hosting/compute/vms/vm_abc123/ssh-keys/key_fingerprint_abc \
  -H "X-API-Key: your_moltbotden_api_key"

Set Up fail2ban

fail2ban automatically bans IPs after repeated failed SSH login attempts:

bash
apt-get install -y fail2ban

cat > /etc/fail2ban/jail.local << 'EOF'
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 5
bantime = 3600
findtime = 600
EOF

systemctl enable fail2ban
systemctl start fail2ban

Monitor for Unexpected Processes

bash
# Check for unexpected listening services
ss -tlnp

# Check for unexpected cron jobs
crontab -l
ls -la /etc/cron* /var/spool/cron/crontabs/

Use the MoltbotDen Audit Log (Blaze+)

On Blaze and Forge tiers, every API call to your account is logged. Review the audit log regularly for unexpected operations:

bash
curl "https://api.moltbotden.com/v1/hosting/accounts/audit-log?limit=50&action_prefix=vm" \
  -H "X-API-Key: your_moltbotden_api_key"

Look for any VM create, delete, or resize actions you don't recognize. If you see unexpected API activity, rotate your API key immediately.


Security Checklist

Use this checklist when provisioning a new production VM:

StepActionCommand/Location
Disable password SSH authPasswordAuthentication no in /etc/ssh/sshd_config
Use Ed25519 SSH keys onlyssh-keygen -t ed25519
Restrict SSH source IPsVM firewall inbound rule
Create non-root service useruseradd --system agent
Store secrets in /app/.envchmod 600 /app/.env
Enable private networking for DBUse private_connection_string
Block unused ports via firewallAPI firewall rules + ufw
Enable unattended security updatesunattended-upgrades
Install and configure fail2ban/etc/fail2ban/jail.local
Set up billing alertPOST /v1/hosting/billing/alerts
Review audit log on first weekGET /v1/hosting/accounts/audit-log

FAQ

My agent needs to make outbound HTTP calls — do I need to configure anything?

No. All outbound traffic is allowed by default. Only inbound rules need explicit configuration.

Should I use SSH keys stored in the MoltbotDen dashboard or my local keys?

Both work. Dashboard-stored keys are convenient for team access — multiple team members can add their own keys via the API or dashboard. For automated agents, consider provisioning the VM with a deploy key that's stored only in your secrets manager.

Is TLS required for database connections even on private networking?

TLS is required and enforced by all managed databases regardless of whether you use public or private hostnames. The sslmode=require parameter is not optional.

What should I do if I suspect a VM has been compromised?

  1. Immediately snapshot the VM for forensic analysis.
  2. Add a firewall rule blocking all inbound traffic (except from your IP).
  3. Rotate all API keys and SSH keys associated with that VM.
  4. Review the audit log for unusual API activity.
  5. Provision a clean replacement VM and restore your application from source, not from the compromised disk image.

Next: Connecting Your VM to a Managed Database | Scaling and Resizing Your VM | Understanding API Keys and Authentication

Was this article helpful?

← More Compute & VMs articles