BACK
JUN 07, 2026Agent Security18 min read

Secure Self-Hosted Hermes

Operating model

Build it around five practical checks, then add capability.

Every step should answer the same practical questions:

  • Who can reach this surface?
  • What can it execute?
  • What can it access?
  • Where can it write?
  • How do I recover if it breaks?
  1. 01

    Reach

    Keep administration private.

    Use SSH keys, close public SSH after Tailscale works, and keep dashboards on localhost, Tailscale, or an SSH tunnel.

  2. 02

    Execute

    Separate the operator from the agent runtime.

    Run Hermes as a restricted user, prefer the Docker backend, and avoid direct host-shell execution for normal agent work.

  3. 03

    Expose

    Keep secrets and mounts narrow.

    Store secrets in .env, forward no environment by default, avoid broad host mounts, and give generated files one /output path.

  4. 04

    Access

    Default-deny every interface.

    Allowlist Telegram or Discord users, limit commands per platform, review MCP/tools, and delay risky integrations.

  5. 05

    Recover

    Assume the system will need rollback.

    Take snapshots after stable phases, keep recovery commands in your notes, monitor gateway/dashboard logs, and test reboot survival before adding sensitive workflows.

Most “install an AI agent on a server” guides dump commands. They get a agent running, then leave the important question unanswered: What did you expose to the internet, to the agent runtime, and to the untrusted content it reads?

You want a private, useful, secure Hermes Agent with Docker-isolated tools, allowlisted Telegram access and an Obsidian vault that syncs back to your laptop.

This is not a guarantee of security. It is a practical personal deployment. Review it against your own threat model before using it with sensitive data.

What and how to do it

A safer self-hosted Hermes Agent setup runs on a Ubuntu VPS, exposes administration only through Tailscale, executes tools through Docker rather than the host shell, stores secrets outside the tool runtime, and restricts Telegram access to allowlisted users.

The target architecture

Target architecture

Private agent stack with one controlled artifact path

01

Mac / Laptop

Local operator

02

Tailscale

Private network

03

Ubuntu VPS

Hardened host

04

Hermes Gateway

Agent interface

05

Docker sandbox

Tool boundary

06

/output

Artifact mount

07

Obsidian vault

Syncthing sync

Public internet

No admin dashboards or SSH surface

Tailscale

SSH, dashboard, and internal services

Docker tools

No host home, no Docker socket, no broad secrets

The design principle is that the agent can work without inheriting your whole server.

My current deployment:

Current deployment

Layer 01

Ubuntu 24.04 VPS on Hetzner

Layer 02

Tailscale for private access

Layer 03

Docker backend for tools

Layer 04

Telegram gateway

Layer 05

Hermes Desktop through an SSH tunnel

Layer 06

Dashboard bound to localhost

Layer 07

Admin surfaces behind Tailscale or localhost

Layer 08

Hermes runs as a non-root user

Layer 09

Obsidian vault synced with Syncthing

Layer 10

Restricted tool surface

Hermes can use tools, run commands, write files, search the web, remember context, and talk through messaging platforms. That makes it useful. It also makes a private deployment closer to a small operations system than a casual chatbot.

Who this setup is for?

This configuration is a strong fit if you want Hermes for:

  • research and deep-search style workflows;
  • personal automation;
  • Telegram or Discord assistant access;
  • Obsidian-based knowledge management;
  • artifact generation on a server that is always online.

Do not use this baseline alone for:

  • financial-system operations;
  • production cloud administration;
  • confidential client or patient data;
  • critical infrastructure;
  • unsupervised high-impact decisions.

For those workflows, add approval layers, audit logs, secrets management, scoped credentials, monitoring, and a second human review process.

Step 1: Start with a boring VPS

Use a mainstream VPS provider and a current Ubuntu LTS image.

Recommended baseline:

ComponentMinimum
OSUbuntu 24.04 LTS
CPU4+ vCPU
RAM8+ GB
Disk100+ GB

Reasonable providers include Hetzner, DigitalOcean, Vultr, and Linode. A Hetzner CPX31/CPX32-class instance is a practical starting point for personal agent workflows.

Do not start with the cheapest box. Hermes is not the only workload. You may also run Docker containers, Syncthing, logs, browsers, Python tooling, and generated artifacts.

Step 2: Use SSH keys only

Create an SSH keypair on your laptop. Upload only the public key to the VPS provider.

Laptop/Mac
  private key: stays local
  public key: uploaded to VPS provider

Use SSH keys from the start when possible. If password SSH is used during bootstrap, disable it after confirming key-based admin access.

Step 3: Separate the admin user from the Hermes user

Create two users:

User role

admin

sudo: yes

purpose: server maintenance

User role

hermes

sudo: no

purpose: run Hermes and related services

This separation matters. If someone abuses Hermes or a tool runtime, the next step should not be sudo.

The hermes user should own the Hermes install and its working directories. The admin user should handle package installation, firewall changes, system upgrades, and recovery tasks.

Step 4: Harden SSH before you install the fun stuff

At minimum:

  • disable root login;
  • disable password login;
  • keep public-key auth enabled;
  • keep a tested admin SSH session open while changing settings;
  • only then reload or restart SSH.

SSH hardening can lock you out. Take a VPS snapshot before and after this phase if your provider supports it.

Step 5: Configure the firewall with a private-network end state

Start with UFW:

Firewall baseline

incoming: deny by default

outgoing: allow by default

initially allow: SSH

The final goal is stricter: SSH should only be reachable over Tailscale, not from the public internet.

That means there are two phases:

  1. Bootstrap phase: public SSH is temporarily allowed so you can set up the machine.
  2. Private phase: after Tailscale works, public SSH is closed and administration happens over the 100.x.x.x Tailscale address.

Do not close public SSH until you have tested Tailscale SSH access from your laptop.

Step 6: Install Tailscale and move administration off the public internet

Tailscale gives you a private network without your own VPN server. For this setup, it keeps dashboards, admin ports, and SSH off the public internet.

After Tailscale is installed and the VPS appears in your tailnet:

  • test SSH to the VPS Tailscale IP;
  • confirm you can administer the server through that route;
  • update UFW so public SSH is no longer open;
  • keep Hermes dashboard/admin access private as well.

Use this mental model:

Network visibility

Public internet

should not see Hermes admin surfaces

Tailscale

can reach SSH / dashboard / internal services

Step 7: Install Docker for tool isolation

Hermes can run terminal/file/code execution through different backends. For a security-conscious VPS, prefer:

yamlterminal:
  backend: docker

Avoid using a direct local shell backend for normal agent work on a server. Docker is not a magic shield, but it creates a boundary between agent tool execution and the host operating system.

This isolates common tool actions such as:

  • shell commands;
  • Python scripts;
  • Node tooling;
  • file writes;
  • package experiments.

You will not make compromise impossible. You can stop routine agent work from getting the same access as the server operator.

Step 8: Install Hermes under /home/hermes, not as root

Install Hermes as the restricted hermes user.

Recommended location:

/home/hermes

Useful verification commands after installation:

bashhermes version
hermes doctor
hermes config path
hermes config env-path

Use the public Hermes installer endpoint:

bashcurl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash

Then run the setup wizard:

bashhermes setup

For provider authentication, use the current Hermes docs and the hermes setup, hermes model, and hermes auth flows. Do not hardcode stale model-provider assumptions.

Step 9: Configure the model provider, but do not confuse model choice with security

Model choice affects quality, cost, latency, and context. It will not fix a bad server architecture.

Hermes supports both OAuth/subscription-backed providers and API-key-backed providers. Some advanced tooling, external evaluation frameworks, and optimization systems may require direct API keys even if normal Hermes usage works through OAuth.

Use lighter daily models for normal operation and stronger models for deep work. Treat the model as one layer. Your security posture comes from:

  • private networking;
  • OS hardening;
  • containerized tool execution;
  • secrets isolation;
  • Telegram allowlisting;
  • approval policies;
  • controlled filesystem mounts.

For operational resilience, configure fallback providers in config.yaml, not .env:

yamlfallback_providers:
  - provider: openrouter
    model: <fallback-model-name>

Choose a model currently supported by your provider. Use fallbacks for outages, rate limits, or cost control, and test them before you depend on them during a long research run.

Step 10: Put secrets in .env, not in tool-visible files

Hermes documentation separates configuration from secrets:

~/.hermes/config.yaml  → normal settings
~/.hermes/.env         → API keys, bot tokens, secrets

That split matters. The tool container should not inherit your whole environment.

Use a tighter posture:

yamlterminal:
  backend: docker
  env_passthrough: []
  docker_forward_env: []

Forward only the variables a workflow needs. If a tool needs a GitHub token, add it on purpose. Do not forward your whole shell environment into an AI-controlled runtime.

Step 11: Use restrictive Docker mounts

Do not mount the host root filesystem into the tool container.

Avoid mounting:

/
/home/hermes
/var/run/docker.sock

Especially avoid the Docker socket. Mounting /var/run/docker.sock usually means the container can control Docker on the host. That can become host-level control.

Prefer a small, intentional artifact mount:

yamlterminal:
  backend: docker
  docker_mount_cwd_to_workspace: false
  docker_forward_env: []
  env_passthrough: []
  docker_volumes:
    - "/home/hermes/output:/output"

Then tell Hermes to write generated files to /output. Any dedicated folder can work. Mount that folder, not the whole home directory. The exact host path is not important. The rule is simple: mount only a dedicated artifacts directory into /output, not the entire home directory.

Step 12: Fix generated-file ownership before it becomes annoying

If Docker writes files as root, your sync tools and editor will complain.

Use Hermes Docker settings so generated files are owned by the host user where possible:

yamlterminal:
  docker_run_as_host_user: true

Use docker_run_as_host_user: true when writing to a synced /output folder, so generated files are owned by the hermes user instead of root. This improves file sync and permissions, but it is not a standalone security control. It should still be paired with narrow mounts and no secret passthrough. Root-owned markdown files do not improve security. They create maintenance work.

Step 13: Configure Telegram with an allowlist

Create a bot through BotFather and keep the token secret. If it leaks, revoke it.

Hermes Telegram setup can be handled interactively:

bashhermes gateway setup

For manual .env configuration:

envTELEGRAM_BOT_TOKEN=<your-bot-token>
TELEGRAM_ALLOWED_USERS=<your-numeric-telegram-id>

Do not use an allow-all configuration for a private AI agent. Telegram is the front door. Lock it.

If the bot is added to groups, also pay attention to:

  • group allowlists;
  • privacy mode;
  • whether the bot sees all messages or only mentions;
  • whether group context is being observed without auto-replying.

The desired gateway posture is default-deny: only explicitly allowed users or paired users should be able to interact with the agent.

The default private deployment should begin with one user ID: yours. For groups, use group allowlists separately and do not assume a private-user allowlist protects group chats automatically.

Allowlisting controls who can reach the bot. It does not mean every allowed user should get every command.

For shared bots, separate:

admin users
regular allowed users
allowed slash commands

Use limited command exposure for non-admin users. Do not assume "allowlisted" means "safe to give full tool access."

For shared bots, configure allow_admin_from and user_allowed_commands so regular users only get specific slash commands. For groups, use the group equivalents: group_allow_admin_from and group_user_allowed_commands.

Step 14: Install the Hermes gateway as a persistent service

A Telegram assistant needs to survive logout and SSH disconnects.

Use Hermes gateway service commands:

bashhermes gateway install
hermes gateway start
hermes gateway status

If you are using a user-level service on Linux, make sure the service can survive logout when required. On systemd systems that may involve enabling linger for the service user.

Then check the service and logs:

bashhermes gateway status
journalctl --user -u hermes-gateway -n 120 --no-pager
sudo systemctl status syncthing-hermes
sudo ufw status verbose

If the gateway is not installed as a systemd user service, check the Hermes log file instead:

bashtail -n 100 ~/.hermes/logs/gateway.log

Step 15: Restrict tools by platform

Do not expose every Hermes tool to every interface by default. A Telegram bot, Discord bot, desktop app, and terminal session do not need the same tool surface.

Run hermes tools to configure individual tool availability per platform. Hermes persists those choices to config.yaml, so this is finer-grained than only enabling or disabling broad toolsets.

Start with a small surface:

  • chat and research tools;
  • file write access only to /output;
  • browser and web tools only when a workflow needs them;
  • no unnecessary messaging or admin integrations;
  • no production cloud tools;
  • no main email or GitHub write access at first.

Treat each new tool as another attack surface. Docker isolation helps, but it does not make a powerful tool safe on every platform.

Treat MCP servers and external tool integrations like privileged plugins. Do not add MCP servers casually. Review what tools they expose, what credentials they need, and whether they can access local files, network services, or secrets.

Step 16: Delay high-risk integrations

Delay sensitive integrations on day one:

  • main Gmail or personal email;
  • production GitHub write access;
  • cloud admin keys;
  • Home Assistant device control;
  • webhook or API server exposure;
  • shared Discord or Slack bots.

Start with Telegram allowlisted to one user, one web search backend, and /output file writing. Add stronger integrations only after the basic setup behaves safely.

Step 17: Make /output the only normal write target

A good artifact system gives generated files one obvious destination.

Use:

Host

/home/hermes/output

Docker

/output

This prevents the common failure mode where the agent creates a file, but the gateway cannot send it or your laptop cannot find it.

For Docker-backed Telegram sessions, Hermes docs warn that attachments are sent by the gateway process, not from inside the container. So the final file path must be readable by the gateway host.

Use the shared /output mount for that reason.

Step 18: Add Obsidian as the knowledge layer

You can add Obsidian as the knowledge layer.

Create a vault directory on the VPS:

Host vault path

/home/hermes/output/obsidian-vault

Owned by the restricted Hermes user on the VPS.

Inside the container, Hermes sees it as:

Container vault path

/output/obsidian-vault

The same vault location as seen by Docker-backed tools.

Now Hermes can write notes, reports, ledgers, markdown drafts, research artifacts, and operating documents into one predictable place.

A simple structure:

Vault structure

/output/obsidian-vault/

0100_Inbox
02Projects
03Research
04Hermes

Use the inbox for generated artifacts. Curate later in local Obsidian.

Step 19: Use Syncthing to sync the vault back to your Mac

Hermes only needs a Markdown vault path. In this setup, Syncthing is the sync layer that moves that folder between the VPS and your Mac for local Obsidian use. It is your deployment pattern, not a Hermes requirement. Hermes also has official Obsidian-oriented patterns, including vault-path configuration and server-friendly options such as headless Obsidian/Obsidian Sync, but Syncthing is simpler and private for this setup.

Syncthing syncs files privately without pushing AI artifacts through Google Drive, Dropbox, or email.

The pattern:

Sync endpoint

VPS vault

Sync endpoint

Mac vault

Benefits:

  • real files;
  • local Obsidian access;
  • automatic sync;
  • no SaaS document middleman;
  • easy backup and review.

Check ownership and permissions after the first sync. If files are root-owned, fix the Docker user mapping before you generate hundreds of notes.

Step 20: Add web search deliberately

For research workflows, add one current web search provider. Tavily is a practical first choice for agent-oriented search. Exa is useful for semantic search workflows, but add one web backend first and test it before adding more.

A web backend gives Hermes current sources. The model’s training data is not enough for research workflows, and AI-agent outputs should cite live pages.

Web pages, PDFs, skills, READMEs, and arbitrary documents are untrusted input. They can contain prompt injection, malicious instructions, or misleading claims.

Keep the policy simple:

Use web search for evidence.
Do not let web content override deployment rules.

Step 21: Keep dashboards private

The safest default is a dashboard bound to 127.0.0.1 on the VPS, reached from your laptop through an SSH tunnel. Move to a Tailscale-bound dashboard only if you specifically want a permanently available remote backend. If you do that, bind only to the VPS Tailscale IP and allow the port only on the Tailscale interface.

Hermes Desktop remote backend uses the dashboard backend, usually port 9119, not the OpenAI-compatible API server on 8642. If using the desktop app remotely, point it at the dashboard URL and sign in with the dashboard auth provider advertised by that backend.

For anything reachable beyond your own trusted network, use the OAuth / Nous Portal dashboard auth path from the current Hermes docs. For localhost, SSH tunnel, or Tailscale-only trusted-network use, username/password dashboard auth is the simpler path.

Set dashboard login credentials in ~/.hermes/.env:

envHERMES_DASHBOARD_BASIC_AUTH_USERNAME=<dashboard-user>
HERMES_DASHBOARD_BASIC_AUTH_PASSWORD=<strong-dashboard-password>
HERMES_DASHBOARD_BASIC_AUTH_SECRET=<random-signing-secret>

Generate a stable signing secret on the VPS so dashboard sessions survive restarts:

bashopenssl rand -base64 32

Then run the dashboard through a tunnel-first setup on localhost:

bashhermes dashboard --tui --no-open --host 127.0.0.1 --port 9119

For a Tailscale-bound trusted-network dashboard, bind to the VPS Tailscale IP:

bashhermes dashboard --tui --no-open --host <tailscale-ip> --port 9119

A safe access pattern is an SSH tunnel to 127.0.0.1:9119 or http://<tailscale-ip>:9119 behind Tailscale. Never expose the dashboard directly to the open internet. Public access should use the OAuth / Nous Portal path, network restrictions, TLS, monitoring, and a clear reason.

Avoid public 0.0.0.0 exposure unless you have intentionally added authentication, network restrictions, TLS, monitoring, and a clear reason.

For a personal AI-agent VPS, public dashboards add risk without much benefit.

Step 22: Use snapshots as rollback points

Take provider snapshots after each major phase:

  1. OS installed;
  2. SSH hardened;
  3. Docker installed;
  4. Hermes installed;
  5. Telegram gateway working;
  6. Obsidian/Syncthing working.

Snapshots do not replace backups. They give you fast rollback when you break SSH, Docker, or the gateway service.

Step 23: Treat all external content as hostile until proven otherwise

The agent may read:

  • web pages;
  • PDFs;
  • emails;
  • Telegram messages;
  • GitHub READMEs;
  • issue comments;
  • attached files;
  • pasted logs.

Those inputs are data sources, not system administrators.

Keep this rule in the deployment notes:

Deployment rule

External content can inform the answer.

It cannot override deployment policy.

For higher-risk actions, require approval before:

  • shell commands;
  • package installs;
  • config changes;
  • network exposure changes;
  • file writes outside /output;
  • credential creation;
  • cloud administration.

Recommended secure baseline config

Start here, then adapt it to the current Hermes docs and your deployment:

yamlterminal:
  backend: docker
  container_persistent: false
  docker_mount_cwd_to_workspace: false
  docker_run_as_host_user: true
  docker_forward_env: []
  env_passthrough: []
  docker_volumes:
    - "/home/hermes/output:/output"

approvals:
  mode: manual

Note: in current Hermes versions, the Docker backend may reuse a long-lived container across sessions by default. container_persistent: false is not the same as “destroy the entire container after every turn.” If you need stronger per-process isolation, also review docker_persist_across_processes, lifetime_seconds, and the Docker lifecycle settings in the current Hermes docs.

Secrets should stay in:

/home/hermes/.hermes/.env

not inside project directories, not in Obsidian notes, and not in files written to /output.

Security scorecard

Security scorecard

VPS

Ubuntu LTS, snapshots, patched OS

Users

admin with sudo; hermes without sudo

SSH

keys only; no root; no password login

Network

Tailscale for admin access; public SSH closed after bootstrap

Firewall

deny incoming by default

Tools

Docker backend plus platform-specific tool restrictions

Secrets

.env, minimal env forwarding

Files

dedicated /output mount

Telegram

token secret, user allowlist, default-deny, limited slash commands for shared bots

Obsidian

synced via Syncthing, not broad host mounts

Dashboard

localhost/Tailscale/tunnel only; Desktop remote points to dashboard port 9119, not API port 8642

Recovery

snapshots after every major phase

I would treat this as a strong personal baseline, not a production compliance architecture.

Failure modes

Common mistakes

01

Running everything as root

Root feels convenient until it fails badly. Use a restricted hermes user.

02

Using backend: local on a VPS

That gives the agent direct shell access to the host. Use Docker for normal tool execution.

03

Forwarding all environment variables

If the tool container receives every secret your shell knows, the container boundary is much weaker.

04

Mounting the whole home directory

It feels convenient and gives the tool runtime far more access than it needs.

05

Making Telegram public

A Telegram bot is an access surface. Use TELEGRAM_ALLOWED_USERS.

06

Exposing dashboards publicly

Use Tailscale or an SSH tunnel. Keep AI-agent admin panels off the public internet by default.

07

Losing files because the gateway cannot see the container path

If Docker writes to /workspace/report.md and the gateway cannot read that path, attachments fail. Use a shared /output mount.

08

Using --yolo

Do not use --yolo for a server-side agent. It bypasses dangerous-command approval prompts. Keep approvals in manual mode unless you have a strong reason and compensating controls.

Hermes docs describe approvals.mode: manual as the default for destructive-command prompts, and off as equivalent to --yolo.

Practical implementation checklist

Implementation checklist

43 deployment checks from bootstrap to rollback.

Create Ubuntu 24.04 VPS
Upload SSH public key
Create admin user with sudo
Create hermes user without sudo
Disable root SSH login
Disable password SSH login
Enable UFW: deny incoming, allow outgoing
Install Tailscale
Confirm SSH over Tailscale
Close public SSH
Install Docker
Install Hermes as /home/hermes user
Run hermes setup
Configure model/provider auth
Set terminal backend to docker
Set env passthrough and docker forwarded env to empty by default
Add /home/hermes/output:/output Docker volume
Configure docker_run_as_host_user where supported
Create Telegram bot through BotFather
Set TELEGRAM_BOT_TOKEN in ~/.hermes/.env
Set TELEGRAM_ALLOWED_USERS to your numeric Telegram ID
Keep gateway access default-deny
Configure slash-command permissions for shared bots
Install gateway service
Verify gateway status
Run hermes tools
Review tool availability with hermes tools
Disable unnecessary tools per platform
Restrict high-risk tools by platform
Keep Telegram tool surface smaller than CLI/Desktop
Review MCP servers and external tool integrations before enabling them
Delay email, GitHub write access, webhooks, Home Assistant, and cloud admin integrations
Create /output/obsidian-vault
Install and pair Syncthing with Mac
Add Tavily or another web search provider
Optional: configure fallback providers in config.yaml
Optional: add egress filtering before sensitive integrations
Optional: add monitoring/log review for gateway and dashboard services
Keep dashboards localhost/Tailscale-only
Set dashboard Basic Auth credentials or configure OAuth / Nous Portal auth before remote Desktop/dashboard use
If using Hermes Desktop remotely, point it to the dashboard backend on port 9119, not API port 8642
Sign in through the dashboard auth provider advertised by the backend
Snapshot after every stable phase

FAQ

Is Docker enough to make Hermes safe?

No. Docker is one layer. You also need a restricted user, minimal mounts, minimal environment forwarding, private networking, gateway allowlists, and careful approval policies.

Should I expose Hermes publicly behind a password?

For a personal deployment, usually no. Use Tailscale or an SSH tunnel. Public exposure should be a deliberate, monitored production decision.

Can I use Telegram safely?

Yes, if you keep the bot token secret and restrict access to numeric allowlisted user IDs. Do not use an allow-all setting for a private agent.

Why use /output?

Because it creates one controlled write zone for generated files. It also solves Docker/gateway path mismatch problems when sending files through Telegram or syncing to Obsidian.

Why Obsidian plus Syncthing?

It keeps the knowledge base as real local files, syncs privately, and avoids turning every AI artifact into a cloud-document workflow.

What port should Hermes Desktop use for a remote backend?

Use the Hermes dashboard backend, usually port 9119, not the OpenAI-compatible API server on 8642. Keep it private through Tailscale or an SSH tunnel, and sign in through the dashboard auth provider. Use OAuth / Nous Portal for public reachability; use username/password dashboard auth only for localhost or trusted-network access.

Can I store client confidential data in this setup?

Not by default. Add stronger controls first: encryption policies, access logging, explicit approvals, data retention rules, backups, monitoring, and professional compliance review.

Bottom line

A self-hosted Hermes Agent can be useful, but the safe version is not “install Hermes on a VPS.” It is a small private AI operations stack.

The secure pattern is:

Secure pattern

VPShardened SSHTailscaleDocker backendrestricted toolsno broad mountsno secret passthroughTelegram allowlistdefault-deny gateway/output artifact mountObsidian/Syncthingsnapshots

That gives you an always-on private AI agent without handing the model your whole server.

Sources