Skip to content
Home› Blog› How to Update Hermes Agent Safely (Backup and Rollback)

How to Update Hermes Agent Safely (Backup and Rollback)

What broke when we moved Hermes Agent instances from 2026.6 to 2026.9, and the update routine we use now: back up /opt/data, pin a tag, check it serves, roll back cleanly.

HostAiHub Team ·Nov 1, 2026 ·12 min read
How to Update Hermes Agent Safely (Backup and Rollback)

Back up the Hermes data directory, change the image to an exact new tag, recreate the container and confirm it actually serves before you walk away. Write the old tag down first, because rolling back is a one-line edit. If an update has already broken your dashboard, the most likely cause is the change that shipped in 2026.7.1: the dashboard now refuses to start on a public bind unless a login is configured.

Short version
  • Snapshot /opt/data (Docker) or ~/.hermes (local) before every update, and open the archive to prove it's usable.
  • Pin an exact tag such as v0.22.5. Pull first, then recreate. Keep main and rc.* tags off any box you rely on.
  • Crossing 2026.7.1? Add the HERMES_DASHBOARD_BASIC_AUTH_* variables with a fixed secret, or the dashboard won't start.
  • Permission denied: /opt/data/SOUL.md after an update or a restore means the files belong to the wrong user. Newer images run as uid 10000.
  • Putting the old tag back rolls back the program, not your data. If the new version migrated anything, a full undo means restoring the snapshot too.

Why Hermes updates bite

We host Hermes Agent for customers at HostAiHub, and moving instances from the June release to the September one cost us several rounds of fixes. This post comes from those fixes and the official docs. Hermes moves fast, and nearly every problem we hit was a contract changing between the image and the data you carry forward.

~2,100merged PRs between v0.21.5 (24 Sep) and v0.21.6 (8 Oct)
8stable releases published in October 2026
<1 daybefore v0.22.2 replaced v0.22.1, which was built from the wrong commit

Two version numbers for one build

Until late September, each GitHub release carried two numbers. The title read "Hermes Agent v0.21.5 (v2026.9.24)", and the Docker tag was the date: nousresearch/hermes-agent:v2026.9.24. Our panel shows both, as "v0.21.5 (2026.9.24)"; the 1Panel build of the image we deploy drops the v. From v0.21.6 on 8 October, Hermes switched to plain SemVer tags. As of November 2026 the newest stable image is v0.22.5.

For date-era builds, match the tag against the release title before assuming you're on the wrong build. And any script that picks "the highest tag" numerically will now decide 2026.9.24 is newer than 0.22.5, because 2026 beats 0. If something of yours sorts tags, check it, or pin by hand.

What actually broke for us

July. When 2026.7.1 shipped, Hermes containers on our compose template started, and the dashboard inside them never served. The reverse proxy returned 502. We read it as a bad release, pinned new deploys to 2026.6.19, and built the health check with automatic rollback that our update button still uses. It took until the end of September to name the real cause. Since that release, the dashboard refuses to start on a non-loopback bind unless an auth provider is configured, and the old HERMES_DASHBOARD_INSECURE flag is accepted and then ignored. Our dashboards bind 0.0.0.0 inside the container, so every newer image sat there declining to start.

The fix. Hermes bundles a basic provider, a login form with a signed session cookie, that switches on from environment variables. We gave it the email and password each customer already had, a signing secret that never changes, and the public URL. We left HERMES_DASHBOARD_INSECURE=1 in the same file on purpose. 2026.6.19 and older need it to start on 0.0.0.0, newer images ignore it, and an automatic rollback reuses the same compose file. Deleting it would have turned a safe rollback into a second broken dashboard.

The first real update, same day. Three smaller things went wrong in a row. Our panel's API call that runs docker compose up gave up after 30 seconds while the server was still downloading an image of more than a gigabyte, so the update died before its health check ran. Then the health check lied: our proxy has its own login and answers 401 without asking the container, so a dead Hermes looked healthy. And a retry against a compose file that already named the new tag needed a restart, because up on an unchanged file does nothing.

Then permissions. With the dashboard serving, the gateway failed with Cannot initialize Hermes home /opt/data: Permission denied: /opt/data/SOUL.md. From 2026.7 the image drops to an unprivileged hermes user, uid 10000. The June images ran as root and wrote everything as root, and the 2026.9.24 build we ran re-owned its own sub-folders at boot but not top-level files like SOUL.md and config.yaml. We now chown the whole data volume to 10000 before swapping the image. Older tags run as root, so a rollback can still read it.

Then SOUL.md again. After the chown, the gateway still logged the same error on every config load. Ownership was right this time, but 2026.6.x had left SOUL.md at mode 0444, read-only for everyone, which root never notices. v0.21 rewrites the old default SOUL.md at boot, and a non-root owner can't write a read-only file. A chmod 0644 cleared it.

October. An instance already on 2026.9.24 was restored from an older daily backup and hit the same SOUL.md error. The snapshot had been taken under 2026.6.x, so it brought root ownership and mode 0444 back with it. Our restore job now re-owns Hermes data to uid 10000 and makes SOUL.md owner-writable after extracting.

None of these were bugs as such. Each was an assumption about the data directory that a new image stopped sharing.

Hermes releases that matter for updates 2026.6.19 19 Jun · v0.17.0 runs as root; INSECURE flag works 2026.7.1 1 Jul · v0.18.0 dashboard needs an auth provider runs as uid 10000 2026.9.24 24 Sep · v0.21.5 last date-style tag; v0.21 rewrites SOUL.md v0.21.6 8 Oct first SemVer tag v0.22.0 14 Oct memory providers move to plugins, auto-migrated v0.22.5 23 Oct latest stable as of Nov 2026
The releases that changed what an update does to a running install; 2026.7.1 is where most self-hosted dashboards fell over. Dates are from the GitHub release notes. Root vs uid 10000 is what we observed on the images we run.

Before you update

Read the release notes, all of them

Open every release between yours and the target on the GitHub releases page and skim for "Docker", "dashboard", "migrated" and "Known issue". The v0.22.0 notes, for example, say memory providers moved into plugins and that existing users are migrated on update or first start. That's a one-way change to your data.

Check the image exists

A GitHub release can appear before the image you pull. The 1Panel build of 2026.9.24 showed up on Docker Hub a few days after the release. Running docker pull with the exact tag answers the question and pre-loads the image, so do it before touching the compose file. Official tags are on Docker Hub.

Back up the data directory, then test the backup

In Docker, everything Hermes knows lives in /opt/data: config.yaml, the .env with your keys, SOUL.md, sessions, memories, skills, cron jobs and logs. The official Docker guide calls it the single source of truth and maps it to ~/.hermes on the host. For a local install, the same tree is ~/.hermes.

# bind mount (host ~/.hermes mounted at /opt/data)
docker compose stop hermes
sudo tar czf hermes-$(date +%F).tar.gz -C ~/.hermes .
docker compose start hermes

# named volume (check the real name with: docker volume ls)
docker compose stop hermes
docker run --rm -v hermes_data:/data -v "$PWD":/backup alpine \
  tar czf /backup/hermes-$(date +%F).tar.gz -C /data .
docker compose start hermes

# local install
hermes backup                    # writes ~/hermes-backup-<timestamp>.zip

Stopping the container keeps the SQLite files consistent inside the tarball. hermes backup doesn't need that step; the CLI reference says it uses SQLite's backup API and is safe while Hermes runs. Testing the backup means opening it: tar tzf hermes-2026-11-02.tar.gz | grep -E 'SOUL.md|config.yaml' should list both. Keep the archive somewhere private, since it contains your API keys.

Updating a Docker install

The safe update loop Snapshot back up /opt/data Pull exact new tag Recreate compose up -d Health check HTTP + logs serves Done keep the snapshot fails Put the old tag back same compose, recreate old tag Still odd? Restore the snapshot (data was migrated)
The loop we'd run by hand. Our panel's update button automates everything from the pull to the old-tag rollback; the snapshot only comes out when the old tag alone isn't enough.

Here's the compose shape we'd suggest for a self-managed box, trimmed to what matters for updates:

services:
  hermes:
    image: nousresearch/hermes-agent:v0.22.5   # exact tag, never latest
    command: gateway run
    restart: unless-stopped
    ports:
      - "127.0.0.1:9119:9119"
    volumes:
      - ~/.hermes:/opt/data
    environment:
      HERMES_DASHBOARD: "1"
      HERMES_DASHBOARD_BASIC_AUTH_USERNAME: "admin"
      HERMES_DASHBOARD_BASIC_AUTH_PASSWORD: "${HERMES_DASH_PASSWORD}"
      HERMES_DASHBOARD_BASIC_AUTH_SECRET: "${HERMES_DASH_SECRET}"  # openssl rand -hex 32, once
      HERMES_DASHBOARD_PUBLIC_URL: "https://hermes.example.com"
      HERMES_DASHBOARD_INSECURE: "1"  # only if you may roll back to 2026.6.19 or older

Leave out the last line unless your rollback target is 2026.6.19 or older. On those images it's how the dashboard starts on 0.0.0.0 at all, with no login, so keep the port on loopback. Current images ignore it with a warning.

The basic provider is the quickest way past the 2026.7 check, but the dashboard docs describe it as fit for a trusted network or a VPN, and recommend the Nous Portal OAuth provider for internet-facing hosts. Ours also sit behind a login at the reverse proxy; the security checklist covers the rest. Without HERMES_DASHBOARD_BASIC_AUTH_SECRET, Hermes makes a random signing key at every start, which signs everyone out on every restart. Set it once; rotating it is how you sign everyone out on purpose.

  1. Write down the current tag. grep image: compose.yaml. That line is your rollback plan.
  2. Take the snapshot from the previous section and check it lists SOUL.md.
  3. Edit the tag and pull. Change image: to the new exact tag and run docker compose pull. The old container keeps serving while a 1 GB+ image downloads.
  4. Recreate. docker compose up -d. If you only changed something outside the file, such as ownership, compose sees nothing to do; use docker compose up -d --force-recreate.
  5. Check it serves, from the right place. curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:9119/ should print a status code, not a connection error. Through a proxy with its own login, send the credentials; the proxy's 401 says nothing about the container.
  6. Read the logs. docker compose logs --since 15m hermes, looking for Permission denied, anything about auth providers, and migration messages.

Coming from 2026.6.x or older? Sort out ownership before the first start on the new image. Current docs say the container fixes volume ownership on first boot; the 2026.9.24 build we ran re-owned its sub-folders and left top-level files alone. For a named volume:

docker run --rm -v hermes_data:/opt/data alpine \
  sh -c 'chown -R 10000:10000 /opt/data && chmod u+w /opt/data/SOUL.md'

For a bind mount, the docs' cleaner route is to set HERMES_UID and HERMES_GID to the folder's owner so the container matches your files. Either way, make sure SOUL.md is writable by whoever owns it.

Updating a local install

For a source install the official path is hermes update, described on the updating page. Since v0.21.7 it follows tagged stable releases rather than every commit on main. By default it snapshots key state (config.yaml, .env, auth, cron jobs) into state-snapshots/, asks about new config options and restarts running gateways.

hermes update --check      # are you behind? changes nothing
hermes backup              # full zip of ~/.hermes first
hermes update              # or: hermes update --backup
hermes config check        # anything missing or stale?
hermes config migrate      # add newly introduced options

The quick snapshot is best-effort: if it fails, the update prints a warning and carries on, so we'd still run hermes backup first. Setting updates.pre_update_backup to full in config.yaml makes every update zip everything. Re-running the installer is for repairs, not updates.

Run hermes update inside the Docker container and it refuses with exit code 2 and prints a docker pull command. That's correct: a container has no source checkout, so the new version has to come from a new image.

Checks after the update

A container that shows "Up" in docker ps proves very little; our 2026.7.1 containers showed Up and served nothing. Run these with docker compose exec hermes …, or straight from the shell on a local install:

  • hermes --version shows the version you meant to install.
  • hermes doctor comes back clean. Reach for hermes doctor --fix only when it reports something.
  • The dashboard loads and you can sign in. Restart once and confirm you're still signed in.
  • One real message gets a real model answer. A broken provider config doesn't stop the dashboard loading.
  • hermes gateway status looks right, and a test message in Telegram or Discord gets a reply. If the bot stays silent, our Telegram setup notes are the place to start.
  • hermes cron list still shows your scheduled jobs.
  • SOUL.md still holds your persona and is writable by its owner: ls -l /opt/data/SOUL.md.
  • The last 15 minutes of logs have no Permission denied lines.

Rolling back

The quick rollback is the update in reverse: put the old tag back in the compose file and run docker compose up -d. If the old version comes up and behaves, you're done, and this is all our automatic rollback does.

Rollback is not time travel Program image tag Data /opt/data old tag working new tag misbehaves old tag back to working update rollback old format snapshot taken migrated on first start still migrated old code, new data stays Only a snapshot restore takes the data back, and it drops whatever changed since.
Changing the tag rolls back the program. Your data only goes back if you restore it, and that costs everything written after the snapshot.

What the tag change can't undo is anything the new version wrote. The v0.22.0 notes describe memory providers being migrated automatically, and the updating docs say plainly that an older release may not read a state.db written by a newer gateway. So treat a rollback as back to working, not back in time. If the old version starts but behaves oddly, restore the snapshot too:

docker compose down
mv ~/.hermes ~/.hermes.after-update      # keep it, don't delete it
mkdir ~/.hermes
sudo tar xzf hermes-2026-11-02.tar.gz -C ~/.hermes
docker compose up -d

Run as root, tar restores the original owners, which suits the version the snapshot came from. Going the other way, restoring an old snapshot under a newer image, is exactly our October case: re-own to uid 10000 and make SOUL.md writable before you start it. Either way you lose everything since the snapshot, sessions and memories included, so keep the moved-aside folder for a few days.

For a local install, the docs' sequence is hermes gateway stop, check out the older release in ~/.hermes/hermes-agent, then restore the pre-update snapshot with /snapshot restore <id> or a full backup with hermes import <backup.zip>. Stop the gateway before importing.

Symptoms, causes and fixes

Each row is something we hit ourselves or that the official docs describe.

SymptomLikely causeFix
502 or no dashboard right after moving to 2026.7.1 or newerNo auth provider on a non-loopback bind, so the dashboard refuses to start. HERMES_DASHBOARD_INSECURE is ignoredSet HERMES_DASHBOARD_BASIC_AUTH_USERNAME, _PASSWORD, _SECRET and HERMES_DASHBOARD_PUBLIC_URL, or, on a local install, bind to 127.0.0.1 and use an SSH tunnel
Signed out of the dashboard after every restartNo signing secret, so each process generates a random oneSet HERMES_DASHBOARD_BASIC_AUTH_SECRET once and keep it
Cannot initialize Hermes home /opt/data: Permission denied: /opt/data/SOUL.md after updating from 2026.6.xFiles written by an image that ran as root; the new one runs as uid 10000chown -R 10000:10000 on the data directory, or match HERMES_UID/HERMES_GID to the owner
Starts, but logs Permission denied: /opt/data/SOUL.md on every config loadSOUL.md left at mode 0444 by 2026.6.x; v0.21 wants to rewrite itchmod u+w (or 0644) on SOUL.md
Same error right after restoring an older backup onto a newer imageThe snapshot brought the old ownership and modes backRe-own and fix SOUL.md after extracting, before starting
hermes update inside the container exits with code 2Image-managed installs refuse to self-updatePull the new tag and recreate the container
Changed something, ran docker compose up -d, nothing happenedCompose file unchanged, so nothing to recreatedocker compose up -d --force-recreate
Health check says fine, agent is downThe check is reading a 401 from the proxy's own loginProbe with credentials, or hit 127.0.0.1:9119 on the host
Update times out halfwayPulling a 1 GB+ image inside a short client timeoutdocker compose pull first, then up -d
A tool insists 2026.9.24 is newer than v0.22.5Numeric sort across the October tag-scheme changePin tags by hand and treat SemVer tags as newer

How often to update

Hermes ships stable releases most weeks, sometimes several in one, and you don't need each on release day. v0.22.1 was built from the wrong commit and replaced by v0.22.2 within a day, and v0.22.2 shipped with a known issue about sessions created under 0.22.1. Waiting two days and reading the next release's notes would have skipped both.

For a personal or small-team agent, check every week or two and update when a release fixes something you use or mentions security. v0.21.6 carried a batch of dashboard authentication hardening fixes, the kind of note that should move you sooner. Stay on exact tags: latest and stable only move on a published release now, but a pinned tag means a routine restart can never change your version. Leave main and the rc.* tags to the people testing them.

How HostAiHub handles it

If you'd rather not own any of this, it's what our Hermes hosting does. Each instance page shows the version you're on and the newest one we offer, with an Update button. Behind it is most of this post, automated:

  • The compose file gets the dashboard login variables if they're missing. Existing passwords and secrets are never rotated, and the INSECURE flag stays for rollback.
  • The data volume is handed to uid 10000 and SOUL.md is made owner-writable before the new image starts.
  • After the swap, we probe the instance through its own login for up to 45 seconds. If it never answers, the previous tag goes back on and gets checked again. Your data volume stays in place through both.
  • Instances get a daily backup, kept for 7 days, plus up to 3 named backups you create on demand. Any of them can be downloaded as a .tar.gz or restored from the panel, and a restore re-owns Hermes data so a newer image can start.

One honest gap: the Update button doesn't take a snapshot of its own. The newest daily backup can be up to a day old, so if you've changed a lot today, press Create backup first. Hermes hosting is $10 a month, $100 a year or $250 for lifetime (pricing), and you bring your own model key; our LLM cost breakdown has rough numbers for that side. Still choosing? OpenClaw vs Hermes covers the differences.

Questions people ask

How do I check which Hermes Agent version I'm running?

Run hermes --version (in Docker, docker compose exec hermes hermes --version) and compare it with the image: line in your compose file. From spring to late September 2026, tags were dates such as 2026.9.24; the GitHub release title gives the matching version, here v0.21.5.

Why does hermes update do nothing in Docker?

Image-managed installs refuse to self-update and exit with code 2, printing a docker pull command instead. You update by pulling a new tag and recreating the container; /opt/data carries over.

What should I back up before updating Hermes?

The whole data directory: /opt/data in Docker (often ~/.hermes on the host), or ~/.hermes for a local install. That covers config, keys, SOUL.md, sessions, memories, skills and cron jobs. Locally, hermes backup zips all of it.

My Hermes dashboard stopped working after an update. What happened?

If you crossed 2026.7.1, the dashboard refuses to start on a non-loopback bind without an auth provider, and HERMES_DASHBOARD_INSECURE no longer helps. Configure the basic provider with a username, password and fixed secret, or, on a local install, bind to 127.0.0.1 and use an SSH tunnel.

Can I roll Hermes Agent back to an older version?

Yes. Put the previous tag back and recreate the container. If the newer version migrated config or state, restore your pre-update snapshot as well, and accept losing anything written since.

Why am I logged out of the Hermes dashboard after every restart?

Without HERMES_DASHBOARD_BASIC_AUTH_SECRET, Hermes makes a new random signing key at each start, which invalidates every session. Generate one with openssl rand -hex 32 and never change it.

Deploy your own AI agent — no servers to manage

We auto-install OpenClaw, Hermes and n8n to your own private, SSL-secured subdomain. Bring your own LLM key.