Backup and restore
Back up a hub or a whole gateway to one archive while chat keeps working, restore it in place or on a new machine, and include PostgreSQL.
hubzoid backup writes a deployment's databases, chat data and runtime state to one archive while people keep chatting. hubzoid restore puts that archive back, on the same machine or a new one. Hub content (AGENTS.md, skills, knowledge, tools) belongs in the hub's git repository and is not part of the archive.
When to use this
Take a backup on a schedule, before every upgrade, and before a maintenance window such as moving a hub to managed grants. Use restore to recover, to roll back an upgrade, or to move a deployment to another path or server.
Quick start
hubzoid backup ./my-hub --out /var/backups/hubzoid-$(date +%F).tar.gz
hubzoid restore /var/backups/hubzoid-2026-09-25.tar.gz --dry-runEach backup records its time in the operational store. hubzoid doctor reports it as backup.age and warns when there is no backup or the last one is more than 7 days old.
hubzoid backup
hubzoid backup [HUB] [OPTIONS]Prop
Type
What the archive contains
| Saved | From |
|---|---|
| Every SQLite database | The operational store, each hub's workflow engine database, the hub database and the chat app's webui.db and vector store. Copied with SQLite's online backup, so each copy is consistent while the hub runs. |
| Chat app data | Uploads and settings in <hub>/.openwebui-data/, or the gateway's data directory. The chat app's model cache is left out because it is rebuilt on demand. |
| Hub runtime state | Each hub's .hubzoid/, .inbound/, logs/ and output/ folders |
A hub in a gateway shares the operational store with every other hub in it, so backing up any one of them saves the gateway's data directory and every registered hub together.
The archive is written with owner-only permissions. It still holds user accounts, password hashes, chats and the chat app's model connection settings, so store it like a secret and copy it off the machine. A backup on the same disk does not survive losing that disk.
What it leaves out
| Not saved | What to do |
|---|---|
| Hub content | Keep it in git. Check it out again before a restore to a new machine. |
.env files | Keep a copy of each one somewhere safe, or pass --include-secrets. |
.hubzoid/artifact_secret | The key that signs download links. Without it, links issued before the restore stop working. Saved with --include-secrets. |
.webui_secret_key | Saved with --include-secrets. Keep WEBUI_SECRET_KEY itself stable across restores, because changing it signs everyone out. |
Database passwords in deployment.json | The gateway manifest is saved with each password replaced by ***, unless --include-secrets. |
| PostgreSQL databases | The command names each one as Not included (PostgreSQL). Dump them with pg_dump, as described below. |
Scheduled work during a backup
A backup never interrupts chat, Slack, MCP or webhooks. Scheduled work is paused around the copy so that run history and output files match.
- Hold new runs
- Settle 5 seconds
- Wait for running work
- Write archive
- Release hold
- The backup sets a hold that stops new scheduled runs in every hub, then waits five seconds so a run that was being queued as the hold began is seen and waited for too.
- It waits up to
--waitseconds for queued and running work to finish. If runs are still going after that, it stops without writing an archive and names them. Try later, cancel them withhubzoid schedule cancel <hub> <run-id>, or pass--wait 0. - With
--wait 0there is no pause and no wait. A run caught part way is reported as interrupted after a restore and runs again at its next slot. - When the archive is written, the hold is released and any task that fell due in the meantime runs once.
- If the backup process dies, the hold expires by itself after the wait time plus three hours. While it is active,
hubzoid doctorreports it underscheduler.health.
Apart from its hold marker and the record of the backup, a backup only reads. It never creates or upgrades a table, which is why you can back up a deployment with a newer Hubzoid before its first start. See upgrading.
hubzoid restore
Stop the hub, or the gateway and all its bridges, first. Restore refuses to replace a database another process is using.
hubzoid restore ARCHIVE [OPTIONS]Prop
Type
Each saved directory goes back to the path it came from. Whatever is at that path now is kept beside it as <name>.pre-restore-<time>, so you can undo a restore by moving those back. Delete them once the restored deployment checks out.
Restore checks the whole archive before it changes anything. It refuses an archive with unsafe entries, one that would write somewhere a backup never saves from (such as a home directory or a folder that is not a hub's state folder, chat app data or a gateway data directory), and targets that overlap each other.
If the gateway manifest was saved with *** in place of passwords, restore says so. hubzoid gateway rewrites the manifest from its environment every time it starts, so start the gateway before its bridges, or put the passwords back by hand.
After a restore, start the hub or gateway, run hubzoid doctor <hub>, and open the Console to check access and recent runs.
Restore to a new path or machine
hubzoid restore backup.tar.gz --move /root/hubs=/srv/hubsAfter a move, restore rewrites the absolute paths that Hubzoid and the chat app store: the gateway manifest, each hub's pointer to it, the schedule state, and the chat app's uploaded-file paths. On a new machine:
Install Hubzoid
Use the version that wrote the archive or a newer one. An older release refuses databases that a newer one has upgraded.
Check out the hubs
Clone the hubs' git repositories at their new paths.
Restore and copy secrets
Run hubzoid restore with --move, then copy each hub's .env into place (unless the archive was made with --include-secrets).
Start and check
Start the hub or gateway, run hubzoid doctor, and sign in.
PostgreSQL
With DATABASE_URL, HUBZOID_OPERATIONAL_DB or HUBZOID_DBOS_DB pointing at PostgreSQL, those databases are not in the archive. Dump each distinct database once, including the chat app's database when DATABASE_URL put it in PostgreSQL.
The schedule hold covers the archive, not the dumps. For a dump and an archive that match exactly, stop the deployment around both:
- Stop the gateway and every bridge, at a time when no scheduled run is going.
- Dump each PostgreSQL database, with the deployment's environment loaded.
- Run
hubzoid backupwith--wait 0, since nothing is running. - Start the deployment again.
pg_dump takes the plain postgresql:// form, so drop +psycopg from a URL written for Hubzoid:
pgurl() {
printf '%s' "$1" | sed 's/+psycopg//'
}
stamp=$(date +%F)
pg_dump --format=custom --file hubzoid-main-$stamp.dump "$(pgurl "$DATABASE_URL")"
pg_dump --format=custom --file hubzoid-operational-$stamp.dump "$(pgurl "$HUBZOID_OPERATIONAL_DB")"
pg_dump --format=custom --file hubzoid-dbos-$stamp.dump "$(pgurl "$HUBZOID_DBOS_DB")"
hubzoid backup ./my-hub --wait 0 --out hubzoid-files-$stamp.tar.gzLeave out any pg_dump line whose variable is unset, is not PostgreSQL, or names a database you already dumped.
Dumps taken while the deployment runs are minutes apart from the archive. A scheduled run can then appear in the run history without its output files, or the reverse, and a chat, upload or access change made in between is only in whichever was taken later.
To restore, stop the deployment, restore each dump into the database it came from, then restore the archive:
pgurl() {
printf '%s' "$1" | sed 's/+psycopg//'
}
pg_restore --clean --if-exists --no-owner --dbname "$(pgurl "$DATABASE_URL")" hubzoid-main-2026-09-25.dump
pg_restore --clean --if-exists --no-owner --dbname "$(pgurl "$HUBZOID_OPERATIONAL_DB")" hubzoid-operational-2026-09-25.dump
pg_restore --clean --if-exists --no-owner --dbname "$(pgurl "$HUBZOID_DBOS_DB")" hubzoid-dbos-2026-09-25.dump
hubzoid restore hubzoid-files-2026-09-25.tar.gzScheduling backups
A daily backup of every hub in a folder, kept for two weeks:
#!/bin/sh
set -e
date=$(date +%F)
cd /opt/hubzoid/agents
for hub in */AGENTS.md; do
name=$(dirname "$hub")
.venv/bin/hubzoid backup "$name" --out "/var/backups/hubzoid-${name}-${date}.tar.gz"
done
find /var/backups -name 'hubzoid-*.tar.gz' -mtime +14 -deleteMake it executable with chmod +x. For a gateway, back up one hub: the archive covers them all. In a container, run the backup inside it, for example docker compose -f docker/docker-compose.yml exec hubzoid hubzoid backup /hub --out /hub/backup.tar.gz. Then ship the archives off the machine with the tool you already use.
Next steps
Security model
How a Hubzoid deployment is protected, covering network exposure, sign-in, fail-closed authorization, secrets, MCP keys, data locations and egress.
Upgrading
Upgrade Hubzoid from 0.9.x to 1.0.1 and apply later releases safely, with what changes, the steps, database migrations and the way back.
