Hermes Agent Backup: Rclone + R2 + Cron Off-Site Guide

# Hermes Agent Backup Guide: Rclone + Cloudflare R2 + Cron for Encrypted Off-Site Backups

If you run Hermes Agent on a VPS, most of its accumulated state — configuration, conversation sessions, memory, skills, and credentials — lives inside the `~/.hermes` directory. Reinstalling the software itself is straightforward. Recovering all that long-built state after a server ban, accidental deletion, or disk failure is the real challenge.

This guide walks through a complete off-site backup strategy: use **Rclone** to package and encrypt the `~/.hermes` directory, upload it to **Cloudflare R2** object storage, and schedule the whole process with **Linux system Cron** so it runs automatically every day. Because the backup relies on the operating system’s cron daemon rather than Hermes’s own scheduler, backups continue even when Hermes Agent itself is temporarily down or misconfigured.

> **Recency note:** This article reflects the Hermes Agent and Rclone feature sets as of mid-2026. CLI commands, menu layouts, and R2 console paths can change over time — always cross-check against the official documentation linked at the end.

## Two Security Corrections Before You Start

Two practices that show up in older tutorials should be avoided:

1. **Never hardcode the encryption password directly inside the backup script.** Anyone with read access to the script can recover the passphrase and decrypt your backups.
2. **Do not use `pkill -f “hermes start”` to stop the agent.** This blunt approach can kill the wrong process and provides no reliable way to restart Hermes afterward. The script below uses the proper `hermes gateway stop` / `hermes gateway start` commands instead.

## Step 1 — Set Up Cloudflare R2

Open the Cloudflare dashboard, navigate to **R2 Object Storage**, and complete three tasks:

1. **Note your Cloudflare Account ID** (found in the dashboard sidebar or URL).
2. **Create a private bucket**, for example `hermes-backup-bucket`.
3. **Create an R2 API token** under “Manage R2 API Tokens,” granting read, write, and delete permissions scoped to the target bucket.

Save the generated **Access Key ID** and **Secret Access Key** somewhere secure. The Secret is shown in full only once — do not paste it into chat windows, commit it to public repositories, or share it in screenshots.

## Step 2 — Install and Configure Rclone

The official Rclone install script works on Debian, Ubuntu, and other common Linux distributions:

“`bash
curl -fsSL https://rclone.org/install.sh | sudo bash
rclone version
“`

If you prefer not to run a remote script, you can install Rclone through your system package manager (`apt`, `dnf`, etc.), though the repository version may lag behind the latest release.

### Launch the interactive configuration wizard:

“`bash
rclone config
“`

Menu numbers can change between Rclone versions, so follow option **names** rather than memorizing numbers:

| Setting | Value |
|—|—|
| Action | `n` (new remote) |
| Remote name | `cf_r2` |
| Storage type | `s3` |
| Provider | `Cloudflare` |
| Access Key ID | *(your R2 key)* |
| Secret Access Key | *(your R2 secret)* |
| Endpoint | `https://.r2.cloudflarestorage.com` |
| Region | `auto` or leave blank |
| ACL | `private` |

Save the configuration and type `q` to exit.

### Verify the connection:

“`bash
rclone lsd cf_r2:
rclone lsf cf_r2:hermes-backup-bucket
“`

If the first command lists your buckets and the second runs without error, authentication and bucket permissions are working correctly.

## Step 3 — Store the Encryption Password Separately

Create a passphrase file readable only by the current user. The placeholder string below **must** be replaced with your own strong password — generate at least 24 random characters using a password manager.

“`bash
mkdir -p “$HOME/.config/hermes-backup”
umask 077
printf ‘%sn’ ‘REPLACE_WITH_YOUR_LONG_RANDOM_PASSWORD’
> “$HOME/.config/hermes-backup/passphrase”
chmod 600 “$HOME/.config/hermes-backup/passphrase”
“`

**Important:** Do not place this password file inside `~/.hermes`. If you do, disaster recovery becomes a catch-22: you need the password to open the backup, but the backup is the only place that contains the password. Keep a second copy in a local password manager or on offline media.

## Step 4 — Create the Backup Script

Place the script in your user home directory rather than `/opt`. This avoids giving a routine backup job elevated root privileges.

“`bash
mkdir -p “$HOME/bin”
nano “$HOME/bin/hermes-r2-backup.sh”
“`

Paste the full script below:

“`bash
#!/usr/bin/env bash
set -Eeuo pipefail
umask 077
PATH=”$HOME/.local/bin:/usr/local/bin:/usr/bin:/bin”

REMOTE_NAME=”cf_r2″
BUCKET_PATH=”hermes-backup-bucket/hermes-agent”
RETENTION_DAYS=30
HERMES_DIR=”${HERMES_HOME:-$HOME/.hermes}”
PASSWORD_FILE=”$HOME/.config/hermes-backup/passphrase”
LOCK_FILE=”${TMPDIR:-/tmp}/hermes-r2-backup.lock”

# Verify prerequisites
command -v rclone >/dev/null || { echo “rclone not installed”; exit 1; }
command -v openssl >/dev/null || { echo “openssl not installed”; exit 1; }
[[ -d “$HERMES_DIR” ]] || { echo “Directory does not exist: $HERMES_DIR”; exit 1; }
[[ -s “$PASSWORD_FILE” ]] || { echo “Password file missing: $PASSWORD_FILE”; exit 1; }

# Prevent overlapping runs
exec 9>”$LOCK_FILE”
flock -n 9 || { echo “A backup is already running; exiting”; exit 0; }

DATE_TAG=”$(date -u +%Y%m%d_%H%M%S)”
TMP_DIR=”$(mktemp -d “${TMPDIR:-/tmp}/hermes-backup.XXXXXX”)”
BACKUP_FILE=”$TMP_DIR/hermes_${DATE_TAG}.tar.gz.enc”
REMOTE_DIR=”${REMOTE_NAME}:${BUCKET_PATH}”
GATEWAY_WAS_ACTIVE=0

cleanup() {
rc=$?
rm -rf — “$TMP_DIR”
if [[ “$GATEWAY_WAS_ACTIVE” -eq 1 ]]; then
hermes gateway start >/dev/null 2>&1 || true
fi
exit “$rc”
}
trap cleanup EXIT INT TERM

# Gracefully stop the gateway if running, to avoid copying a mid-write SQLite DB
if command -v systemctl >/dev/null
&& systemctl –user is-active –quiet hermes-gateway.service; then
GATEWAY_WAS_ACTIVE=1
hermes gateway stop
fi

# -C is critical: the archive stores .hermes/… so restore won’t
# create an extra home/user directory layer.
tar -C “$(dirname “$HERMES_DIR”)” -czf – “$(basename “$HERMES_DIR”)”
| openssl enc -aes-256-cbc -salt -pbkdf2 -iter 200000
-pass “file:$PASSWORD_FILE” -out “$BACKUP_FILE”

# copyto pins the exact remote filename, avoiding path ambiguity
rclone copyto “$BACKUP_FILE” “$REMOTE_DIR/$(basename “$BACKUP_FILE”)”
–s3-no-check-bucket

# Read-back verification: confirm the object actually landed in R2
rclone lsf “$REMOTE_DIR” –files-only | grep -Fx “$(basename “$BACKUP_FILE”)” >/dev/null

# Retention: delete files older than the policy and clean empty dirs
rclone delete “$REMOTE_DIR” –min-age “${RETENTION_DAYS}d”
rclone rmdirs “$REMOTE_DIR” –leave-root

printf ‘Backup %s finished: %sn’ “$DATE_TAG” “$(basename “$BACKUP_FILE”)”
“`

Make it executable and run it once manually to verify:

“`bash
chmod 700 “$HOME/bin/hermes-r2-backup.sh”
“$HOME/bin/hermes-r2-backup.sh”
rclone lsl cf_r2:hermes-backup-bucket/hermes-agent
“`

You should see an object named like `hermes_YYYYMMDD_HHMMSS.tar.gz.enc`. Only when that file appears with a non-zero size is the first verification complete.

### How the script protects your data

– **AES-256-CBC encryption** with PBKDF2 key derivation (200,000 iterations) — your data is unreadable without the passphrase, even if R2 is compromised.
– **`flock` locking** — prevents two backup runs from colliding if a previous run is slow.
– **Graceful gateway handling** — stops the gateway before copying SQLite databases, then restarts it in the `cleanup` trap so it comes back even on error.
– **Read-back verification** — the script re-lists the remote directory to confirm the upload succeeded, so a silent upload failure can never masquerade as a successful backup.
– **Automatic retention** — deletes objects older than `RETENTION_DAYS` (default 30) to control storage costs.

## Step 5 — Schedule Daily Backups with System Cron

Edit the **current Hermes user’s** crontab — never root’s crontab, or `$HOME`, the Rclone config, and the Hermes directory will all point to the wrong user:

“`bash
crontab -e
“`

Run once daily at 2:00 AM:

“`cron
0 2 * * * “$HOME/bin/hermes-r2-backup.sh” >> “$HOME/.hermes/hermes_backup.log” 2>&1
“`

Or every 12 hours for more frequent protection:

“`cron
0 */12 * * * “$HOME/bin/hermes-r2-backup.sh” >> “$HOME/.hermes/hermes_backup.log” 2>&1
“`

Confirm the task is registered and check recent logs:

“`bash
crontab -l
tail -n 100 “$HOME/.hermes/hermes_backup.log”
“`

This uses **Linux system Cron**, not `hermes cron`. Backups are disaster-recovery infrastructure and should be decoupled from the application being backed up.

## Step 6 — Full Restore on a New VPS

On the replacement server, first install Hermes Agent, Rclone, and OpenSSL, then reconfigure the same `cf_r2` remote. **Do not** run `hermes setup` to overwrite the existing configuration before restoring.

“`bash
curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash
curl -fsSL https://rclone.org/install.sh | sudo bash
rclone config
“`

Recreate the password file using the **same passphrase** from the original setup:

“`bash
mkdir -p “$HOME/.config/hermes-backup”
umask 077
printf ‘%sn’ ‘YOUR_ORIGINAL_ENCRYPTION_PASSWORD’
> “$HOME/.config/hermes-backup/passphrase”
chmod 600 “$HOME/.config/hermes-backup/passphrase”
“`

Find and download the most recent backup:

“`bash
REMOTE_DIR=”cf_r2:hermes-backup-bucket/hermes-agent”
LATEST=”$(rclone lsf “$REMOTE_DIR” –files-only | sort | tail -n 1)”
[[ -n “$LATEST” ]] || { echo “No backups found”; exit 1; }
rclone copyto “$REMOTE_DIR/$LATEST” “$HOME/$LATEST”
“`

Decrypt to a temporary directory and inspect the structure before replacing anything:

“`bash
RESTORE_DIR=”$(mktemp -d)”
openssl enc -d -aes-256-cbc -pbkdf2 -iter 200000
-pass “file:$HOME/.config/hermes-backup/passphrase”
-in “$HOME/$LATEST” -out “$RESTORE_DIR/hermes.tar.gz”
tar -xzf “$RESTORE_DIR/hermes.tar.gz” -C “$RESTORE_DIR”
test -f “$RESTORE_DIR/.hermes/config.yaml”
ls -la “$RESTORE_DIR/.hermes”
“`

Once the directory looks correct, swap it in. Keep the old directory for a rollback path:

“`bash
hermes gateway stop || true
[[ -d “$HOME/.hermes” ]]
&& mv “$HOME/.hermes” “$HOME/.hermes.before-restore-$(date +%s)”
mv “$RESTORE_DIR/.hermes” “$HOME/.hermes”
chmod -R go-rwx “$HOME/.hermes”
hermes doctor
hermes gateway start
hermes gateway status
“`

After restoring, verify that model credentials, Telegram/Discord channels, cron jobs, and skills all function correctly. Remember that the system crontab and Rclone configuration live outside `~/.hermes`, so they must be reconfigured manually on the new machine.

## Step 7 — Four Tests to Run Before Going Live

A backup strategy is only trustworthy once it has been exercised end to end.

1. **Upload test** — Run the script manually and confirm the R2 object size is greater than zero.
2. **Decryption test** — Download a backup, decrypt it in a temporary directory, and confirm `.hermes/config.yaml` is present and readable.
3. **Cron test** — Temporarily set the schedule to a few minutes in the future and confirm the job runs correctly in a non-interactive environment (where `rclone` and `hermes` are still found on `PATH`).
4. **Full restore drill** — Perform a complete recovery on at least one other test machine. A backup that has never been restored is only “probably useful,” not proven.

### Additional retention recommendation

You can configure a **lifecycle rule** on the R2 bucket as a second-layer retention policy. Regardless of whether you enable cloud-side cleanup, download one backup copy per month to a local hard drive. R2 protects against VPS single-point failure; a local copy protects against account lockout, token revocation, or cloud-storage outages.

## Risk and Recency Note

Software evolves. The exact Rclone menu prompts, Cloudflare console layout, Hermes CLI flags, and encryption defaults described here may shift with new releases. Before relying on this setup in production, validate each step against the current official documentation:

– **Rclone** — Cloudflare R2 configuration docs
– **Cloudflare R2** — official object storage documentation
– **Hermes Agent** — CLI command reference and Gateway documentation

Treat your first few scheduled runs as supervised tests. Review the backup log regularly, and run a restore drill quarterly so the procedure stays fresh and you catch breakage caused by upstream changes before it matters.