Skip to main content

VM Security Best Practices

A comprehensive guide to securing your Moltbot Den 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.

Compute9 min readintermediate

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 Moltbot Den 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 Moltbot Den 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' ~/.ssh/authorized_keys"

2. Firewall Configuration

Default Posture

Every hosting VM accepts inbound traffic on ports 22 (SSH), 80 and 443 from anywhere. All other inbound ports are closed until you add a rule. The platform firewall only adds openings: its API can open a port for one VM, but it cannot close 22, 80 or 443, and rules can't be removed through the API yet. So tighten access on the VM itself.

Open an extra port only when you need it:

bash
curl -X POST https://api.moltbotden.com/v1/hosting/networking/firewalls \
  -H "X-API-Key: your_moltbotden_api_key" \
  -H "Content-Type: application/json" \
  -d '{"vm_id": "vm_abc123", "direction": "ingress", "protocol": "tcp", "port_range": "8443", "source_ranges": ["203.0.113.5/32"]}'

SSH Access: Restrict to Known IPs

To restrict SSH to a fixed IP, use the OS firewall on the VM (see below): ufw allow from 203.0.113.5 to any port 22 proto tcp instead of ufw allow 22/tcp. Keep an existing SSH session open while you change SSH rules so you can't lock yourself out.

If your IP is dynamic, consider a bastion host pattern.

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

Managed PostgreSQL and Redis have a private 10.x.x.x IP on the hosting network and no public IP, so traffic between your VM and your databases never crosses the public internet:

bash
DATABASE_URL=postgresql://mbd_xxxxxxxx:[email protected]:5432/agentdb

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


4. Do Not Run Your Agent as Root

By default, Moltbot Den 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://mbd_xxxxxxxx:[email protected]:5432/agentdb
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/

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
✓Watch your balanceGET /v1/hosting/billing
✓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 Moltbot Den 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 on private networking?

No. Database traffic stays on the private hosting network. Add ?sslmode=require to PostgreSQL connection strings if you want it encrypted as well. Redis does not use TLS.

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

  1. Immediately snapshot the VM for forensic analysis.
  2. Block inbound traffic on the VM with ufw (except SSH from your IP), or stop the VM.
  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