Securing Your VPS for Hermes Agent
A practical baseline for running Hermes Agent safely on a VPS.
- # AUTHOR
- : ALEXIS EVERAGE
- # DATE
- : 26/06/2026
- # READ
- : 12 MIN

Who this is for
This guide is for engineers and self-hosters running Hermes Agent on a VPS. It assumes you are comfortable with Linux, SSH, Docker, systemd, and firewall rules.
This is not a complete security manual. It is a practical baseline for deploying Hermes without leaving obvious holes.
Hermes changes quickly. As of June 26, 2026, the official docs cover the gateway, Docker, dashboard, API server, secrets, approvals, sandboxing, and security controls. Verify exact config keys and commands against the current Hermes Agent documentation.
Threat model
A VPS running Hermes is more than chatbot hosting. Hermes can receive messages, call tools, keep memory, use API keys, run terminal commands, schedule work, and interact with external services. Treat it as an automation service with access to sensitive systems.
Assume attackers may try to:
- brute-force SSH or exploit exposed services
- reach the Hermes dashboard or API server
- steal
.envfiles, model API keys, bot tokens, OAuth tokens, or gateway credentials - send prompt-injection content through chat, files, web pages, email, or MCP/tool responses
- trick Hermes into running commands, leaking secrets, changing config, or installing persistence
- abuse Docker socket access, broad mounts, forwarded environment variables, or sudo permissions
The goal is not "perfect security." The goal is to make compromise harder, limit blast radius, and make suspicious behavior visible.
Start with a hardened VPS
Use a supported OS release and keep the base system boring.
sudo apt updatesudo apt upgradesudo adduser --disabled-password --gecos "" hermessudo adduser <your-admin-user> sudosudo apt upgrade may prompt for confirmation. Do not run Hermes as root. Use a dedicated hermes user or the official Docker container with a dedicated data directory. Do not give the hermes user SSH keys or sudo unless you have a specific operational need.
If Hermes will run only as a service account and no one needs an interactive shell as that user, consider setting its shell to /usr/sbin/nologin after the data directory and service files are in place.
Enable or verify automatic security updates. On Ubuntu, check:
systemctl status unattended-upgradesapt-config dump APT::Periodic::Unattended-UpgradeYou want daily package list updates and unattended security upgrades enabled unless you have a deliberate patching process.
Firewall and network exposure
Default to denying inbound traffic:
sudo ufw default deny incomingsudo ufw default allow outgoingsudo ufw enablesudo ufw status verboseAllowing outbound traffic is the pragmatic default for package updates, model APIs, messaging gateways, Tailscale, and web access. It is not the tightest security posture. Because Hermes can execute tools and terminal commands, outbound allowlisting can materially reduce the blast radius of prompt injection, token theft, or a reverse-shell attempt.
If you can maintain the rules, consider restricting egress to the destinations Hermes actually needs: DNS, NTP, OS package mirrors, model providers, your messaging platform, your VPN or mesh network, and any explicit webhook/API endpoints. A middle ground is routing Hermes through a controlled proxy and alerting on new outbound destinations.
If you have a static admin IP, restrict SSH to it:
sudo ufw allow from <your-static-ip-or-vpn-cidr> to any port 22 proto tcpDo not replace that with sudo ufw allow 22 unless you have accepted public SSH exposure. If you do not have a static IP, use a VPN, bastion host, or private mesh network.
Tailscale is a practical option for many self-hosters. Install it on your workstation and VPS, SSH over the Tailscale IP, then restrict SSH to the Tailscale interface:
sudo ufw allow in on tailscale0 to any port 22 proto tcpBefore removing any public SSH rule, test SSH over the Tailscale IP in a separate terminal session and keep the original session open. If the Tailscale connection, ACLs, or firewall rule is wrong, deleting the public SSH rule first can lock you out.
Only after the Tailscale SSH path is verified, remove the public SSH rule. The exact delete command must match your original rule, so list numbered rules first:
sudo ufw status numberedsudo ufw delete <rule-number>A broader rule such as sudo ufw allow in on tailscale0 allows all inbound ports from mesh peers. That may be acceptable on a private, tightly controlled tailnet, but scope rules to specific ports if your Tailscale network includes contractors, shared devices, or other systems you do not fully trust.
Tailscale can remove the need to expose SSH, the Hermes dashboard, or the Hermes API to the public internet. It is free for personal use with plan limits, but it becomes a trust boundary: protect the Tailscale account with MFA, remove old devices, and evaluate the dependency if you have regulatory or data residency constraints.
SSH hardening
Use SSH keys. Before disabling password login, make sure your public key is actually installed for the admin user:
ls -ld ~/.sshls -l ~/.ssh/authorized_keysssh -o PreferredAuthentications=publickey -o PasswordAuthentication=no <your-admin-user>@<server-ip-or-hostname>The .ssh directory should usually be mode 700, and authorized_keys should usually be mode 600. The default OpenSSH key file is .ssh/authorized_keys; if your image uses a different location, make that explicit with AuthorizedKeysFile.
After confirming key-only login works, disable password and root login.
Add these to /etc/ssh/sshd_config or a file under /etc/ssh/sshd_config.d/:
PermitRootLogin noPasswordAuthentication noKbdInteractiveAuthentication no# Older OpenSSH releases may use ChallengeResponseAuthentication instead.# ChallengeResponseAuthentication noPubkeyAuthentication yesAuthorizedKeysFile .ssh/authorized_keysAllowUsers <your-admin-user>MaxAuthTries 3On older Ubuntu/Debian images, KbdInteractiveAuthentication may be unavailable and ChallengeResponseAuthentication no may be the equivalent setting. Do not set UsePAM no as a generic hardening step on Ubuntu/Debian. PAM affects account and session handling; keep the distro default unless you understand the service-level consequences.
Then validate syntax, reload, and test a new login before closing your original session:
sudo sshd -tsudo systemctl reload sshssh -o PreferredAuthentications=publickey -o PasswordAuthentication=no <your-admin-user>@<server-ip-or-hostname>sudo sshd -t only checks syntax. It does not prove your authorized key works, your AllowUsers entry is correct, or your firewall still allows the path you expect. Keep the original SSH session open until the new key-only session succeeds.
If SSH remains public, add rate-limiting with fail2ban or an equivalent tool:
sudo apt install fail2bansudo systemctl enable --now fail2bansudo fail2ban-client status sshdAlso verify the service and jail health:
sudo fail2ban-client statussudo tail -n 80 /var/log/fail2ban.logsudo journalctl -u fail2ban --no-pager -n 80On Ubuntu/Debian, the SSH jail may read /var/log/auth.log or systemd journal entries depending on release and backend. If the sshd jail is not listed, or Fail2Ban logs show backend/log-path errors, check your distribution's jail configuration under /etc/fail2ban/jail.d/ and confirm failed SSH attempts are actually being counted.
If you use Tailscale, you can either keep normal OpenSSH and restrict it with UFW, or evaluate Tailscale SSH. Keep provider console access available for recovery.
Install Hermes with least privilege
Create and protect the Hermes data directory:
sudo -iu hermesmkdir -p ~/.hermeschmod 700 ~/.hermesHermes stores config, sessions, memory, logs, skills, and secrets under ~/.hermes or /opt/data in the Docker image. Treat that directory as sensitive.
If you use Docker, avoid publishing Hermes ports publicly. Prefer loopback or Tailscale-only bindings:
ports: - "127.0.0.1:8642:8642" - "127.0.0.1:9119:9119"The current Hermes Docker docs use 8642 for gateway/API examples and 9119 for the dashboard when enabled. Treat those numbers as examples, not invariants. Before copying a Compose snippet, verify the ports for your installed Hermes version and enabled services.
Dashboard and API server
The Hermes API server and dashboard are powerful interfaces. Do not expose either directly to the internet.
Options that keep access private:
- access them over Tailscale
- bind them to
127.0.0.1and use an SSH tunnel - put them behind Caddy/nginx with HTTPS and authentication
- use a provider firewall so only trusted source IPs can reach them
If you need public HTTPS, use a reverse proxy such as Caddy or nginx with Let's Encrypt certificates and authentication.
Be careful with CORS if you enable browser-based API access. CORS controls which browser origins may read API responses. A wildcard policy is not authentication, and it can make it easier for an unintended web page or browser tool to interact with an API the user can reach. Keep allowed origins narrow unless the Hermes docs for your use case require otherwise.
CORS is enforced by browsers. It does not stop curl, scripts, server-to-server requests, compromised hosts, or anyone who can reach the API outside a browser. Use it as browser-origin control, not as a substitute for authentication, private networking, firewall rules, or reverse-proxy access controls.
The Hermes docs describe dashboard behavior as version- and configuration-dependent. Treat the dashboard as private unless you have intentionally placed authentication in front of it and verified it from an external network.
Prompt injection is a real risk
Prompt injection means untrusted content changes the agent's behavior. Direct injection can come from a chat user. Indirect injection can come from a web page, document, email, repository file, image, MCP server response, or tool output.
Mitigations reduce impact; they do not make prompt injection impossible.
Use Hermes security controls:
approvals: mode: manual cron_mode: deny security: redact_secrets: true website_blocklist: enabled: true domains: - "admin.example.com" - "*.internal.example.com"Production VPS deployments should not use approvals.mode: off, --yolo, /yolo, or HERMES_YOLO_MODE=1. Use a sandboxed terminal backend. Leave SSRF protection enabled. Do not set security.allow_private_urls: true on an internet-facing gateway unless you explicitly accept the risk of prompt-injected URLs reaching private network services.
Review permanent command allowlists. Treat "always approve" as a configuration change, not a convenience click.
Detection matters too. Watch logs for unexpected tool calls, credential access, new outbound connections, or commands that do not match normal use.
Docker isolation and Docker socket risk
Hermes recommends container backends for production gateway deployments because commands run inside an isolated environment. That is useful, but Docker access itself is powerful.
Anyone who can control /var/run/docker.sock can often get root-equivalent access to the host by starting privileged containers or mounting host paths. Do not mount the Docker socket into the Hermes container unless you fully understand the consequence. Do not casually add the hermes user to the docker group on a shared system.
Prefer these patterns:
- rootless Docker where practical
- a separate worker VM for command execution
- tightly controlled rootful Docker on a single-purpose VPS
- no host-sensitive mounts
- explicit CPU, memory, and disk limits
Check terminal backend settings against the current docs. The intent should look like this:
terminal: backend: docker docker_forward_env: [] container_cpu: 1 container_memory: 4096Only forward environment variables that the agent truly needs.
Secrets
Put secrets in ~/.hermes/.env, not in config.yaml, memory files, skills, chat messages, or shell history.
chmod 600 ~/.hermes/.envUse separate API keys for Hermes where possible. Scope them narrowly. Rotate them after tests, staff changes, suspected leaks, or major config changes.
Hermes supports Bitwarden Secrets Manager. The current docs describe a model where one bootstrap token lives in .env, and Hermes pulls provider keys from Bitwarden at process startup. That can make rotation easier, but the bootstrap token is still sensitive.
Be careful with Docker environment variables. Values passed with -e, environment:, or env_file: can be visible through docker inspect to anyone with Docker daemon access. This is another reason Docker socket access should be limited.
Environment variables can also be exposed through process metadata such as /proc/<pid>/environ to sufficiently privileged local users. For higher-assurance deployments, prefer secrets mounted as files, Bitwarden Secrets Manager, age/SOPS-managed files, Docker Swarm secrets, or another secrets mechanism that does not rely on broad process environment exposure.
Gateway access
Lock down who can talk to Hermes.
- configure explicit platform allowlists
- avoid
GATEWAY_ALLOW_ALL_USERS=true - separate admins from normal users
- revoke users who no longer need access
- verify identities with
/whoami - require mentions in shared channels unless a channel is dedicated to Hermes
A compromised messaging account can become a path to agent control, especially when that account can approve commands.
Updates and backups
Keep three layers current:
- OS packages
- Docker engine/images or language dependencies
- Hermes Agent
Use the update and diagnostic path that matches your installation. Some Hermes installs may support hermes update and hermes doctor; container-based installs may rely more on fresh image pulls and release notes. Treat CLI maintenance commands as version- and install-method-dependent, and verify them against the official docs for your installed version.
Enable pre-update backups if supported by your installed version:
updates: pre_update_backup: true backup_keep: 5Back up ~/.hermes, but encrypt it. It may contain secrets, memory, sessions, logs, and pairing data. Practical options include age, gpg --symmetric, or an encrypted backup tool such as restic.
For example, with age:
tar -C ~ -czf - .hermes | age -r <your-age-public-key> -o hermes-backup.tgz.ageOr with GPG symmetric encryption:
tar -C ~ -czf - .hermes | gpg --symmetric --cipher-algo AES256 -o hermes-backup.tgz.gpgStore recovery keys outside the VPS and test restores on a disposable VPS.
Document your provider, OS image, DNS records, firewall rules, Tailscale setup, Docker Compose file, Hermes config, and restore commands outside the VPS. Your recovery plan should cover rebuilding the VPS, restoring Hermes data, rotating tokens if the old host may be compromised, and verifying gateway allowlists before resuming service.
Logging, monitoring, and alerts
Watch both inbound and outbound behavior.
Treat Hermes logs as sensitive. Logs may contain message text, tool arguments, command output, memory snippets, URLs, filenames, and occasionally secrets if a token is passed through a tool or redaction fails. Restrict log access, rotate logs, and be careful before shipping them to a third-party logging service.
Useful checks:
sudo ufw status verbosess -tulpndocker psdocker logs --tail 100 hermeshermes gateway statusMonitor:
- SSH failures and new sudo users
- firewall rule changes
- unexpected listening ports
- Hermes gateway restarts
- changes to
.env,config.yaml, command allowlists, skills, and cron jobs - new outbound connections from the container or host
- disk, memory, CPU, and backup failures
- log growth, log retention, and log shipping destinations
For outbound visibility, consider provider flow logs, Tailscale flow logs where available, host firewall logging, or lightweight network monitoring. Unexpected outbound traffic from an agent is worth investigating.
If Hermes runs in Docker, configure log rotation through the Docker logging driver or your Compose/service manager so logs cannot fill the disk.
Periodic security review
Monthly, verify:
- SSH password login and root login are disabled
- SSH is restricted to static IP, VPN, Tailscale, or bastion access
- no Hermes dashboard/API port is publicly reachable unless intentionally protected
approvals.modeis notoffGATEWAY_ALLOW_ALL_USERSis not enabled- Docker socket access is limited and understood
.envpermissions are correct- forwarded environment variables are minimal
- backups restore successfully
- old bot users, Tailscale devices, and API keys are revoked
- Hermes, OS packages, and container images are current
hermes doctordoes not report unresolved issues
Maintenance checklist
Daily
- Check service health.
- Review urgent alerts.
- Confirm disk space is healthy.
Weekly
- Review failed SSH attempts and gateway logs.
- Check public ports from outside the VPS.
- Apply pending updates or confirm unattended updates ran.
- Review unexpected outbound traffic.
Monthly
- Test backup restore.
- Review Hermes users, pairing approvals, cron jobs, skills, and command allowlists.
- Rotate high-value credentials where practical.
- Re-read the current Hermes security docs for changed defaults.
Sources and further reading
- Hermes Agent documentation
- Hermes Security
- Hermes Docker
- Hermes Configuration
- Hermes Messaging Gateway
- Hermes Secrets
- Hermes Bitwarden Secrets Manager
- OpenSSH
sshd_config(5) - Ubuntu firewall documentation
- Ubuntu automatic updates
- Docker Engine security
- Docker rootless mode
- Tailscale: secure Ubuntu server with UFW
- Tailscale SSH
- OWASP LLM01: Prompt Injection
- Caddy Automatic HTTPS
- Fail2Ban project
- age file encryption
- restic backups