Skip to content

Agent Host — First Install & Hardening

Complete procedure for provisioning a fresh Hostinger VPS KVM2 running Ubuntu 24.04 LTS to run the Hermes Agent with security hardening.

Prerequisites:

  • Hostinger account with ability to provision VPS
  • SSH key pair generated on your local machine (~/.ssh/hostinger_vps_ed25519)
  • Cloudflare account with the domain registered
  • 30–45 minutes

On your Mac:

Terminal window
ssh-keygen -t ed25519 -C "francois@ducatillon.net-hostinger-vps" -f ~/.ssh/hostinger_vps_ed25519
# Press Enter twice (no passphrase)

This creates:

  • Private key (~/.ssh/hostinger_vps_ed25519) — for your Mac
  • Public key (~/.ssh/hostinger_vps_ed25519.pub) — for Hostinger

Backup private key to Credential Vault (Bitwarden).

Edit ~/.ssh/config:

Host hostinger-vps
HostName <YOUR_VPS_IP>
User hermes
IdentityFile ~/.ssh/hostinger_vps_ed25519

Replace <YOUR_VPS_IP> with your actual VPS IP (you’ll get this from Hostinger after provisioning).


Phase 2: OS Installation (Hostinger Dashboard)

Section titled “Phase 2: OS Installation (Hostinger Dashboard)”
  1. Log in to Hostinger dashboard

  2. Create new VPS → select Ubuntu 24.04 LTS

  3. Set root password (required by Hostinger, even though we’ll disable it)

  4. Paste SSH public key in the OS installation wizard:

    Terminal window
    cat ~/.ssh/hostinger_vps_ed25519.pub # Copy entire output

    Paste into Hostinger’s “SSH Key” field

  5. Complete installation → note your VPS IP address

Edit ~/.ssh/config with the actual VPS IP you received.


SSH into VPS as root:

Terminal window
ssh -i ~/.ssh/hostinger_vps_ed25519 root@<YOUR_VPS_IP>
Terminal window
apt update && apt upgrade -y

Takes 1–2 minutes. If prompted about restarting services, press Enter to accept defaults.

Terminal window
useradd -m -s /bin/bash hermes
Terminal window
mkdir -p /home/hermes/.ssh
cp /root/.ssh/authorized_keys /home/hermes/.ssh/authorized_keys
chmod 700 /home/hermes/.ssh
chmod 600 /home/hermes/.ssh/authorized_keys
chown -R hermes:hermes /home/hermes/.ssh
Terminal window
echo "hermes ALL=(ALL) NOPASSWD: ALL" | tee /etc/sudoers.d/hermes

Disable root login and password auth:

Terminal window
sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
sshd -t && echo "Config OK" || echo "CONFIG ERROR"
systemctl restart ssh

✅ Should see “Config OK”.

Terminal window
ufw allow 22/tcp
ufw --force enable
ufw status

Expected output:

Status: active
To Action From
-- ------ ----
22/tcp ALLOW Anywhere
22/tcp (v6) ALLOW Anywhere (v6)
Terminal window
apt install -y unattended-upgrades
dpkg-reconfigure -plow unattended-upgrades

Select “Yes” when prompted.

Terminal window
sudo reboot

VPS will disconnect. Wait 30 seconds.

Open a new terminal and test:

Terminal window
ssh -i ~/.ssh/hostinger_vps_ed25519 hermes@<YOUR_VPS_IP>

✅ Should connect without password.

Do NOT proceed if root still accepts SSH. Verify SSH hardening worked before continuing.


Why a Tunnel? (Security & Architecture Reasoning)

Section titled “Why a Tunnel? (Security & Architecture Reasoning)”

The problem without Tunnel: A VPS has a public IP address in a datacenter. If port 9119 is opened directly to the internet, the dashboard becomes accessible to anyone who knows the IP + port. Even with authentication (OTP), the attack surface is much larger.

What a Tunnel does:

  • VPS initiates an outbound connection to Cloudflare edge (inbound connections are not accepted)
  • Cloudflare acts as a proxy — it receives internet traffic and routes it through the tunnel back to the VPS
  • The firewall remains restrictive, allowing only SSH (port 22) inbound — all other ports are blocked
  • Tunnel traffic is encrypted — anyone sniffing the wire sees encrypted tunnel traffic, not plaintext HTTP

Why this matters:

  1. Zero inbound exposure — VPS firewall stays closed (UFW blocks everything except SSH)
  2. Cloudflare’s CDN & DDoS protection — malicious traffic is absorbed before reaching the VPS
  3. Email OTP authentication — Cloudflare Access gates the dashboard; even if someone discovers the URL, authentication is required
  4. Portable infrastructure — if the VPS provider changes, the tunnel simply points to the new IP

Real-world analogy:

  • Without tunnel: VPS door is open to the street. Anyone can attempt to enter.
  • With tunnel: VPS door stays locked. Cloudflare operates a reception desk on the street that verifies credentials before allowing entry.
  1. https://dash.cloudflare.com/ → select the domain → Tunnels (left sidebar)
  2. Click Create a tunnel
  3. Type: Cloudflared
  4. OS: Debian
  5. Architecture: 64-bit
  6. Name: hermes-<provider> (e.g., hermes-hostinger)
  7. Save

On VPS (as hermes user):

Terminal window
sudo mkdir -p --mode=0755 /usr/share/keyrings
curl -fsSL https://pkg.cloudflare.com/cloudflare-public-v2.gpg | sudo tee /usr/share/keyrings/cloudflare-public-v2.gpg >/dev/null
echo 'deb [signed-by=/usr/share/keyrings/cloudflare-public-v2.gpg] https://pkg.cloudflare.com/cloudflared any main' | sudo tee /etc/apt/sources.list.d/cloudflared.list
sudo apt-get update && sudo apt-get install cloudflared

From Cloudflare dashboard, copy the Debian/64-bit service install command:

Terminal window
sudo cloudflared service install <TOKEN>

(Replace <TOKEN> with the actual token from the dashboard.)

Terminal window
sudo systemctl start cloudflared
sudo systemctl status cloudflared

✅ Should show active (running) with tunnel connections established.

In Cloudflare dashboard → your tunnel → Add a routePublished application:

FieldValue
Subdomainhermes
DomainYour domain (e.g., ducatillon.net)
Path(leave empty)
TypeHTTP
Service URLhttp://localhost:9119

Save. DNS CNAME created automatically.


VPS ready for Hermes Agent installation

Check:

  • SSH access as hermes (key-based) ✅
  • Root SSH disabled ✅
  • UFW firewall active (port 22 only) ✅
  • cloudflared running ✅
  • Tunnel route configured ✅

Phase 5: Prepare Credentials (Local Machine)

Section titled “Phase 5: Prepare Credentials (Local Machine)”

Before SSH into the VPS, gather the secrets you’ll need for Hermes Agent configuration.

  1. Go to https://discord.com/developers/applications

  2. Click New Application → name: Hermes

  3. Left sidebar → Bot section → Add Bot

  4. Copy the Tokensave to Bitwarden immediately (visible only once)

  5. Disable “Public Bot” if you want it private (optional)

  6. Bot permissions needed:

    • Send Messages
    • Read Message History
  7. Create a personal Discord server for testing (if you don’t have one):

    • Discord app → Add a serverCreate My Own → name it Hermes Lab
    • Later, you’ll invite the bot to this server

You now have: DISCORD_BOT_TOKEN (saved in Credential Vault)

  1. Go to https://openrouter.ai
  2. Sign up with email (free tier)
  3. Dashboard → API Keys (left sidebar)
  4. Click Create New Key or Create API Key
  5. Name it: hermes-agent-openrouter-key
  6. Optional: Set credit limit (recommended: €15/month to prevent surprise bills)
  7. Copy the API Keysave to Bitwarden immediately (visible only once)

You now have: OPENROUTER_API_KEY (saved in Credential Vault)

Both secrets are now ready for Phase 6 (Hermes Agent install).


Before starting, you need:

  • Discord bot token — from Developer Portal (saved in Credential Vault)
  • OpenRouter API key — from openrouter.ai (saved in Credential Vault)
  • Google Personal OAuth2 refresh token — from Google Cloud Console (roadmap for Drive Bridge; can skip day one if using web-only)

SSH into VPS as hermes:

Terminal window
ssh hostinger-vps

Install via the official curl installer:

Terminal window
curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash

This:

  • Installs Python 3.11, uv, Node.js, ripgrep, ffmpeg
  • Creates ~/.hermes/ directory structure
  • Installs Hermes Agent to ~/.local/bin/hermes

When done, reload your shell:

Terminal window
source ~/.bashrc

Verify installation:

Terminal window
hermes --version

✅ Should show version number (e.g., hermes-agent 0.14.0).

Create ~/.hermes/.env with your secrets:

Terminal window
cat > ~/.hermes/.env << 'EOF'
# Discord Gateway
DISCORD_BOT_TOKEN=<YOUR_DISCORD_BOT_TOKEN>
# LLM Provider
OPENROUTER_API_KEY=<YOUR_OPENROUTER_API_KEY>
# Optional: Google Drive (Drive Bridge — roadmap)
# GOOGLE_OAUTH2_REFRESH_TOKEN=<YOUR_GOOGLE_REFRESH_TOKEN>
# Optional: GitHub (for code projects — roadmap)
# GITHUB_PAT=<YOUR_GITHUB_PAT>
EOF

Replace <YOUR_DISCORD_BOT_TOKEN> and <YOUR_OPENROUTER_API_KEY> with the values from Bitwarden.

Secure the file:

Terminal window
chmod 600 ~/.hermes/.env

Hermes supports 200+ models via OpenRouter. Choose a default:

Terminal window
hermes model

Follow the prompts to select:

  1. Provider: OpenRouter
  2. Model: Start with a free model (e.g., Llama 3.1 8B via MiMo) for testing

Start the setup wizard:

Terminal window
hermes setup

When prompted, select:

  • Gateway: Discord
  • Discord Token: (will prompt you to paste)
  • Model: Your choice from step 6.3

The wizard creates ~/.hermes/config.yaml with all settings.

Copy your agent identity context:

Terminal window
cat > ~/.hermes/SOUL.md << 'EOF'
# [Paste the SOUL.md draft from ../soul-draft.md here]
EOF

(See SOUL.md draft template — copy the entire content.)

Link to your architecture glossary:

Terminal window
cat > ~/.hermes/CONTEXT.md << 'EOF'
# [Paste the CONTEXT.md from ../CONTEXT.md here]
EOF

(See CONTEXT.md glossary — this gives Hermes the domain language.)

6.7 Create systemd service (optional, for auto-restart)

Section titled “6.7 Create systemd service (optional, for auto-restart)”

For production, run Hermes as a systemd user service:

Terminal window
cat > ~/.config/systemd/user/hermes-gateway.service << 'EOF'
[Unit]
Description=Hermes Agent Gateway
After=network.target
[Service]
Type=simple
ExecStart=%h/.local/bin/hermes gateway
Restart=on-failure
RestartSec=10
[Install]
WantedBy=default.target
EOF
systemctl --user daemon-reload
systemctl --user enable hermes-gateway
systemctl --user start hermes-gateway

Verify:

Terminal window
systemctl --user status hermes-gateway

Send a test message via Discord:

  1. Open Discord app → find “Hermes Lab” server → bot should be there
  2. Send message: @Hermes hello
  3. Check response in Discord (should reply)

✅ Gateway is working.

On the VPS, try the interactive CLI:

Terminal window
hermes --tui

Type a question (e.g., What is my purpose?) → press Enter.

✅ Hermes should respond using your selected model.

Type /quit to exit.


The web dashboard should now be available at:

https://hermes.ducatillon.net

(Protected by Cloudflare Access — you’ll see an OTP prompt.)

✅ You should see:

  • Session history
  • Logs
  • Chat interface
  • Analytics
  • Cron jobs

Set up optional Uptime Kuma for monitoring Hermes services:

Terminal window
# Roadmap: Uptime Kuma setup
# For now, manually check service status:
systemctl --user status hermes-gateway

Next: Hermes Agent Configuration & Automation (roadmap)


Hermes Agent is live

Checklist:

  • hermes --version shows version number
  • ~/.hermes/.env configured with Discord + OpenRouter
  • hermes --tui responds to questions
  • Discord bot receives messages and replies
  • Web dashboard accessible at hermes.ducatillon.net
  • systemctl --user status hermes-gateway shows running (if using systemd)

IssueDiagnosisFix
Install script failsPython 3.11 not availableRun: apt install -y python3.11 python3.11-venv
hermes: command not foundPATH not updatedRun: source ~/.bashrc and try again
Discord bot doesn’t replyToken missing or invalidCheck ~/.hermes/.env has correct DISCORD_BOT_TOKEN
OpenRouter API errorsKey invalid or expiredRegenerate key at openrouter.ai and update ~/.hermes/.env
Dashboard not accessibleTunnel not routing to port 9119Verify in Cloudflare dashboard: route points to http://localhost:9119
Systemd service failsConfig not readableRun: journalctl --user -u hermes-gateway -n 50 to see errors

Next: Install Hermes Agent


IssueDiagnosisFix
SSH key auth not workingPublic key not in hermes’s authorized_keysVerify: cat /home/hermes/.ssh/authorized_keys contains your public key
Root SSH still worksSSH config not restartedRun: systemctl restart ssh
UFW blocks connectionsRule syntax errorRun: ufw status verbose and verify port 22 is ALLOW
Tunnel not connectingCloudflare token missing/expiredRe-run service install command from dashboard
Tunnel connects but dashboard unreachableHermes not listening on 9119 yetNormal — will be resolved after Hermes agent install

  • SSH key stored in Credential Vault
  • Root password disabled or randomized
  • Password auth disabled (SSH key only)
  • UFW firewall enabled (port 22 only)
  • Unattended-upgrades configured
  • Cloudflare Tunnel connected
  • Hermes user can sudo without password