Back to Article List

Hermes Agent production hardening checklist

Hermes Agent production hardening checklist

The minute someone other than you can send messages to your agent, the threat model changes. Now you're hosting a system that takes user input, makes shell calls, runs Python, hits external APIs and writes files. Each of those is a place where a malicious or careless input can cause real damage. None of the steps below is exotic on its own; together they're the difference between an agent you'd trust with anything and one that quietly ruins your week.

I've ordered the steps roughly by impact. The first three are the ones that block 90 percent of the realistic threats. The rest tighten the screws further. The assumption is you've already done a basic install, either via the Ubuntu install guide or the friendlier 1-click route in our Hermes Agent VPS setup guide. The agent should answer messages on at least one channel before you start tightening. Hardening before the basic setup works is putting the cart before the horse.

Run as a non-root user with limited filesystem reach

Don't run Hermes as root. Ever. The default install creates a per-user setup, which is right; but check that your systemd unit (if you wrote one) starts the service as a normal user, not as root.

ps aux | grep hermes tells you. If the process owner is root, fix it now. The unit file from the systemd guide uses User=youruser for exactly this reason.

Beyond just running as a normal user, restrict what that user can write to. Systemd makes this easy with ProtectSystem=strict and ProtectHome=read-only, with ReadWritePaths=/home/youruser/.hermes as the explicit exception. The result: even if the agent decides to rm -rf /, the kernel refuses because the user can only write to its own data dir.

For Docker setups, the equivalent is running the container with a non-root USER directive in the Dockerfile (the default Hermes image already does this) and mounting only the volumes you really need.

Turn on command approval for shell tools

Hermes can run shell commands. By default, it asks for approval before each command except for ones on a baseline allowlist (read-only file ops, certain git commands, etc.). For production use, tighten this further.

hermes config set approval_mode interactive makes the agent prompt for approval on every shell command. Slow but safe; the agent waits for you to confirm each invocation.

hermes config set approval_mode allowlist with an explicit allowlist file is the better pattern for production. The agent runs commands on the allowlist without asking and refuses anything else.

The allowlist itself lives at ~/.hermes/approvals.yaml (or wherever your config.yaml points it). A reasonable starting allowlist:

allowed_commands:
  - "ls *"
  - "cat /home/youruser/projects/**"
  - "git status"
  - "git diff"
  - "git log *"
  - "rg *"
  - "find /home/youruser/projects/* -type f"
  - "wc -l *"
denied_commands:
  - "rm *"
  - "sudo *"
  - "chmod *"
  - "chown *"
  - "curl * | bash"
  - "curl * | sh"

The deny list takes precedence over the allow list, so even if a command matches both, deny wins. Be liberal with the deny list; you can always lift a restriction later if it's blocking real work.

Sandbox tool execution

Hermes runs Python and shell commands inside the same process by default. For most personal use, fine. For production, sandbox each tool execution.

The simplest sandbox is the Docker terminal backend. Set hermes config set terminal.backend docker; from then on, every shell command and Python execution runs inside a fresh Docker container that gets thrown away afterwards. The container has its own filesystem, its own network namespace and zero ability to affect the host except through explicit volume mounts.

Configure the sandbox image:

hermes config set terminal.docker.image hermes-sandbox:latest
hermes config set terminal.docker.timeout 30s
hermes config set terminal.docker.network none

The network none setting blocks the sandboxed code from reaching the internet, which is the right default for code-execution tools. If a specific skill legitimately needs network access, it can opt in by overriding the network setting on a per-call basis.

For more isolation, use the SSH backend pointed at a dedicated worker VPS or use the Daytona/Modal serverless backends, which give you ephemeral compute environments per execution. Both are documented in the official docs; pick the one that matches your operational tolerance.

Filter secrets out of LLM context

The agent's system prompt and tool outputs go to your LLM provider. If a tool output contains secrets (an API key in a config file the agent read, a password in an env dump), those secrets get sent to the LLM. Even if the provider is reputable, it's a leak in waiting.

Hermes has a secret-filter feature that scrubs known patterns (API keys, JWTs, AWS credentials, SSH private keys, generic high-entropy strings) before sending tool output to the LLM. Enable:

hermes config set security.filter_secrets true
hermes config set security.filter_secrets_replace "[REDACTED]"

The filter runs on tool output before it's added to context, so the agent sees [REDACTED] in place of the real secret. Some skills may break if they were relying on extracting credentials from tool output; that's working as intended.

Add custom patterns for your own secret formats:

hermes config set security.filter_secrets_patterns "[a-zA-Z0-9]{40}"

This catches generic 40-character tokens (common for personal access tokens). Be careful with patterns that are too broad; you'll redact things that aren't secrets and confuse the agent.

Lock down inbound webhook auth

If you're running messaging integrations that use inbound webhooks (Slack, custom HTTP integrations, GitHub repo events), each webhook source has a way to sign incoming requests. Use it.

For Slack: set SLACK_SIGNING_SECRET in ~/.hermes/.env with the value from your Slack app's "Basic Information" page. Hermes verifies every incoming Slack request against this secret; unsigned or wrongly-signed requests get rejected. If you don't set the secret, Hermes won't reject anything, which means anyone who finds your webhook URL can send the agent prompts.

For GitHub: the equivalent is GITHUB_WEBHOOK_SECRET. Same idea; configure it in your GitHub repo's webhook settings and in your Hermes env and the agent verifies HMAC signatures on incoming events.

For custom integrations: if the source supports any signing scheme (HMAC, JWT, mutual TLS), use it. If it doesn't, put the webhook endpoint behind a long random URL path and rotate the path occasionally; not great, but better than a guessable path.

Allowlist messaging users

By default, Hermes will respond to anyone who can reach a configured channel. For a public Telegram bot that's the point. For a private agent, restrict who it talks to.

Telegram allowlist:

hermes config set messaging.telegram.allowed_users "[123456789, 987654321]"

The numbers are Telegram user IDs (not usernames; usernames change). Get yours by messaging @userinfobot on Telegram, which replies with your numeric ID.

Discord allowlist:

hermes config set messaging.discord.allowed_users "[1234567890123456, 9876543210987654]"

Discord user IDs are 18-digit numbers; enable Discord developer mode (Settings → Advanced → Developer Mode), right-click a user, "Copy User ID".

For Slack, the equivalent is workspace-level: only members of your workspace can DM the bot, which is usually restrictive enough on its own. Tighten further by limiting which channels the bot is in.

Audit logging

Production agents should log every tool call, every approval prompt and every external API call. Hermes writes most of this to journald already (via the hermes service unit), plus to its session DB. Make sure both are turned on at sufficient verbosity:

hermes config set logging.level info
hermes config set logging.audit.enabled true
hermes config set logging.audit.path /var/log/hermes/audit.jsonl

The audit log is JSON-lines, one event per line, easy to ship to a log aggregator. Common patterns:

tail -f /var/log/hermes/audit.jsonl | jq 'select(.tool == "shell")'   # all shell calls
tail -f /var/log/hermes/audit.jsonl | jq 'select(.event == "approval")' # approval decisions

If you ship logs off-host (Loki, Vector, Datadog, plain syslog), audit events go with them automatically. You want this for forensic recovery if anything ever goes wrong; the audit log tells you exactly what the agent did, in what order, with what inputs.

Update cadence and supply chain

Hermes ships a major release every week or two. Subscribing to the GitHub releases atom feed gives you a notification when each lands, with release notes describing what changed. Read them before updating.

For the actual update, don't blindly run hermes update on production. Update a staging instance first, run it for a day under representative load, then promote. The update process itself is fast (under a minute) but skill behaviour can subtly shift between releases, especially around the learning loop. Catching changes in staging is cheap; catching them in production is expensive.

Pin to a specific tag in production rather than tracking main:

cd ~/.hermes/hermes-agent
git fetch --tags
git checkout v0.12.0

Then run uv pip install -e . to refresh the venv against the pinned commit.

For Docker installs, the same idea: pin to :v0.12.0 rather than :latest. Bump deliberately when you've tested.

Network controls

The agent only needs outbound HTTPS to LLM providers, GitHub (for skill imports) and your messaging platforms. Outbound to anywhere else is suspicious and worth blocking.

The cheapest egress filter is iptables / nftables on the host. The Hermes process doesn't need outbound port 25 (SMTP), port 23 (telnet), port 22 (SSH out) or random high ports beyond what the LLM provider uses. Block those at the host level so a compromised tool can't exfiltrate data via, say, an unexpected SMTP send.

For inbound, only the messaging webhooks and (optionally) the dashboard need to be reachable. Everything else should be firewalled. UFW makes this readable:

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp        # SSH (or your management port)
sudo ufw allow 80/tcp        # for Let's Encrypt HTTP-01
sudo ufw allow 443/tcp       # for HTTPS to Nginx
sudo ufw enable

The dashboard's port 9119 is bound to localhost (per the Nginx + HTTPS guide), so no UFW rule for it. The Slack webhook port (8765 or wherever you've configured it) is reached via Nginx on 443, not directly.

Backups and incident readiness

Even with all of the above, things break. Backups are how you recover. The full backup pattern is in the backups guide; the production-relevant bits are: nightly encrypted backups off-host, restore tested at least quarterly, retention policy clearly written down somewhere a colleague can find.

If you suspect compromise, the playbook is: stop the gateway, freeze the data dir, copy the audit log somewhere safe, rotate every credential the agent has access to (every API key, every bot token, every SSH key in ~/.hermes) and only then start triaging what happened. The audit log plus your backup history together usually tell you when the compromise started and what it did.

Multi-user agent considerations

If your agent serves multiple users (a team Slack bot, a customer-facing assistant), the threat surface is broader because each user is a potential injection vector. Two extra layers worth adding.

First, per-user rate limits. Hermes supports them via messaging.<platform>.rate_limit config keys; cap each user to, say, 30 messages per hour. Stops one user from running the bill up or hammering the bot with prompt-injection attempts.

Second, per-user prompt sanitisation. The current Hermes versions have a basic prompt-injection filter (catches obvious "ignore previous instructions" patterns), enabled by default. Verify it's on with hermes config get security.prompt_injection_filter. Don't rely on it to catch every variant; the better defence is to keep the agent's tool surface minimal in multi-user mode.

The 1-click route

The LumaDock Hermes Agent VPS template ships with the systemd unit configured for non-root execution, UFW pre-configured with sensible defaults and SSH key-only access enabled. The hardening steps in this article apply to any Hermes install; a 1-click template just means several of them are done before you start.

Your idea deserves better hosting

24/7 support 30-day money-back guarantee Cancel anytime
Platební období

1 GB RAM VPS

$3.99 Save  25 %
$2.99 Měsíčně
  • 1 vCPU AMD EPYC
  • 30 GB NVMe úložiště
  • Neomezený přenos dat
  • IPv4 a IPv6 v ceně Podpora IPv6 momentálně není dostupná ve Francii, Finsku a Nizozemsku.
  • 1 Gbps síť
  • Správa firewallu
  • Monitorování serveru zdarma

2 GB RAM VPS

$5.99 Save  17 %
$4.99 Měsíčně
  • 2 vCPU AMD EPYC
  • 30 GB NVMe úložiště
  • Neomezený přenos dat
  • IPv4 a IPv6 v ceně Podpora IPv6 momentálně není dostupná ve Francii, Finsku a Nizozemsku.
  • 1 Gbps síť
  • Správa firewallu
  • Monitorování serveru zdarma

6 GB RAM VPS

$14.99 Save  33 %
$9.99 Měsíčně
  • 6 vCPU AMD EPYC
  • 70 GB NVMe úložiště
  • Neomezený přenos dat
  • IPv4 a IPv6 v ceně Podpora IPv6 momentálně není dostupná ve Francii, Finsku a Nizozemsku.
  • 1 Gbps síť
  • Správa firewallu
  • Monitorování serveru zdarma

AMD EPYC VPS.P1

$7.99 Save  25 %
$5.99 Měsíčně
  • 2 vCPU AMD EPYC
  • 4 GB RAM paměť
  • 40 GB NVMe úložiště
  • Neomezený přenos dat
  • IPv4 a IPv6 v ceně Podpora IPv6 momentálně není dostupná ve Francii, Finsku a Nizozemsku.
  • 1 Gbps síť
  • Automatická záloha v ceně
  • Správa firewallu
  • Monitorování serveru zdarma

AMD EPYC VPS.P2

$14.99 Save  27 %
$10.99 Měsíčně
  • 2 vCPU AMD EPYC
  • 8 GB RAM paměť
  • 80 GB NVMe úložiště
  • Neomezený přenos dat
  • IPv4 a IPv6 v ceně Podpora IPv6 momentálně není dostupná ve Francii, Finsku a Nizozemsku.
  • 1 Gbps síť
  • Automatická záloha v ceně
  • Správa firewallu
  • Monitorování serveru zdarma

AMD EPYC VPS.P4

$29.99 Save  20 %
$23.99 Měsíčně
  • 4 vCPU AMD EPYC
  • 16 GB RAM paměť
  • 160 GB NVMe úložiště
  • Neomezený přenos dat
  • IPv4 a IPv6 v ceně Podpora IPv6 momentálně není dostupná ve Francii, Finsku a Nizozemsku.
  • 1 Gbps síť
  • Automatická záloha v ceně
  • Správa firewallu
  • Monitorování serveru zdarma

AMD EPYC VPS.P5

$36.49 Save  21 %
$28.99 Měsíčně
  • 8 vCPU AMD EPYC
  • 16 GB RAM paměť
  • 180 GB NVMe úložiště
  • Neomezený přenos dat
  • IPv4 a IPv6 v ceně Podpora IPv6 momentálně není dostupná ve Francii, Finsku a Nizozemsku.
  • 1 Gbps síť
  • Automatická záloha v ceně
  • Správa firewallu
  • Monitorování serveru zdarma

AMD EPYC VPS.P6

$56.99 Save  21 %
$44.99 Měsíčně
  • 8 vCPU AMD EPYC
  • 32 GB RAM paměť
  • 200 GB NVMe úložiště
  • Neomezený přenos dat
  • IPv4 a IPv6 v ceně Podpora IPv6 momentálně není dostupná ve Francii, Finsku a Nizozemsku.
  • 1 Gbps síť
  • Automatická záloha v ceně
  • Správa firewallu
  • Monitorování serveru zdarma

AMD EPYC VPS.P7

$69.99 Save  20 %
$55.99 Měsíčně
  • 16 vCPU AMD EPYC
  • 32 GB RAM paměť
  • 240 GB NVMe úložiště
  • Neomezený přenos dat
  • IPv4 a IPv6 v ceně Podpora IPv6 momentálně není dostupná ve Francii, Finsku a Nizozemsku.
  • 1 Gbps síť
  • Automatická záloha v ceně
  • Správa firewallu
  • Monitorování serveru zdarma

EPYC Genoa VPS.G1

$4.99 Save  20 %
$3.99 Měsíčně
  • 1 vCPU AMD EPYC Gen4 AMD EPYC Genoa 4. generace 9xx4 s 3,25 GHz nebo podobným výkonem, založený na architektuře Zen 4.
  • 1 GB DDR5 RAM paměť
  • 25 GB NVMe úložiště
  • Neomezený přenos dat
  • IPv4 a IPv6 v ceně Podpora IPv6 momentálně není dostupná ve Francii, Finsku a Nizozemsku.
  • 1 Gbps síť
  • Automatická záloha v ceně
  • Správa firewallu
  • Monitorování serveru zdarma

EPYC Genoa VPS.G2

$12.99 Save  23 %
$9.99 Měsíčně
  • 2 vCPU AMD EPYC Gen4 AMD EPYC Genoa 4. generace 9xx4 s 3,25 GHz nebo podobným výkonem, založený na architektuře Zen 4.
  • 4 GB DDR5 RAM paměť
  • 50 GB NVMe úložiště
  • Neomezený přenos dat
  • IPv4 a IPv6 v ceně Podpora IPv6 momentálně není dostupná ve Francii, Finsku a Nizozemsku.
  • 1 Gbps síť
  • Automatická záloha v ceně
  • Správa firewallu
  • Monitorování serveru zdarma

EPYC Genoa VPS.G4

$25.99 Save  27 %
$18.99 Měsíčně
  • 4 vCPU AMD EPYC Gen4 AMD EPYC Genoa 4. generace 9xx4 s 3,25 GHz nebo podobným výkonem, založený na architektuře Zen 4.
  • 8 GB DDR5 RAM paměť
  • 100 GB NVMe úložiště
  • Neomezený přenos dat
  • IPv4 a IPv6 v ceně Podpora IPv6 momentálně není dostupná ve Francii, Finsku a Nizozemsku.
  • 1 Gbps síť
  • Automatická záloha v ceně
  • Správa firewallu
  • Monitorování serveru zdarma

EPYC Genoa VPS.G6

$48.99 Save  31 %
$33.99 Měsíčně
  • 8 vCPU AMD EPYC Gen4 AMD EPYC Genoa 4. generace 9xx4 s 3,25 GHz nebo podobným výkonem, založený na architektuře Zen 4.
  • 16 GB DDR5 RAM paměť
  • 200 GB NVMe úložiště
  • Neomezený přenos dat
  • IPv4 a IPv6 v ceně Podpora IPv6 momentálně není dostupná ve Francii, Finsku a Nizozemsku.
  • 1 Gbps síť
  • Automatická záloha v ceně
  • Správa firewallu
  • Monitorování serveru zdarma

EPYC Genoa VPS.G7

$74.99 Save  27 %
$54.99 Měsíčně
  • 8 vCPU AMD EPYC Gen4 AMD EPYC Genoa 4. generace 9xx4 s 3,25 GHz nebo podobným výkonem, založený na architektuře Zen 4.
  • 32 GB DDR5 RAM paměť
  • 250 GB NVMe úložiště
  • Neomezený přenos dat
  • IPv4 a IPv6 v ceně Podpora IPv6 momentálně není dostupná ve Francii, Finsku a Nizozemsku.
  • 1 Gbps síť
  • Automatická záloha v ceně
  • Správa firewallu
  • Monitorování serveru zdarma

1 vCPU AMD Ryzen 9

$13.99 Save  29 %
$9.99 Měsíčně
  • Dedikované CPU 4.5GHz AMD Ryzen 9 7950X s nativní frekvencí CPU 4.5 GHz.
  • 4 GB DDR5 RAM paměť
  • 50 GB NVMe úložiště
  • Neomezený přenos dat
  • IPv4 a IPv6 v ceně Podpora IPv6 momentálně není dostupná ve Francii, Finsku a Nizozemsku.
  • 1 Gbps síť
  • Automatická záloha v ceně
  • Správa firewallu
  • Monitorování serveru zdarma

2 vCPU AMD Ryzen 9

$25.99 Save  19 %
$20.99 Měsíčně
  • Dedikované CPU 4.5GHz AMD Ryzen 9 7950X s nativní frekvencí CPU 4.5 GHz.
  • 8 GB DDR5 RAM paměť
  • 100 GB NVMe úložiště
  • Neomezený přenos dat
  • IPv4 a IPv6 v ceně Podpora IPv6 momentálně není dostupná ve Francii, Finsku a Nizozemsku.
  • 1 Gbps síť
  • Automatická záloha v ceně
  • Správa firewallu
  • Monitorování serveru zdarma

8 vCPU AMD Ryzen 9

$92.99 Save  30 %
$64.99 Měsíčně
  • Dedikované CPU 4.5GHz AMD Ryzen 9 7950X s nativní frekvencí CPU 4.5 GHz.
  • 32 GB DDR5 RAM paměť
  • 400 GB NVMe úložiště
  • Neomezený přenos dat
  • IPv4 a IPv6 v ceně Podpora IPv6 momentálně není dostupná ve Francii, Finsku a Nizozemsku.
  • 1 Gbps síť
  • Automatická záloha v ceně
  • Správa firewallu
  • Monitorování serveru zdarma

FAQ

How do I tell if my approval allowlist is too restrictive without watching every conversation?

Hermes logs every denied tool call to the audit log. grep '"event":"command_denied"' /var/log/hermes/audit.jsonl | tail -50 shows recent denials. Patterns of repeated denials for the same kind of command tell you the agent legitimately needs that command for the work you're asking it to do; either add it to the allowlist or rephrase your prompts to avoid the pattern. A small number of denials is healthy; many denials per hour means your allowlist needs tuning.

Your agent runs wild. Your bill doesn't.

Easily deploy Hermes in one click on Ubuntu 24.04 with AMD EPYC, NVMe storage and unmetered bandwidth. The price stays the same whatever the agent does, no setup fees, no overage charges and no tier traps.

GPU products are in high demand at the moment. Fill the form to get notified as soon as your preferred GPU server is back in stock.