Take a backup that is actually complete#
You will end up with one encrypted archive, off the router, that can rebuild the box: its configuration, its accounts and their password hashes, its API tokens, its two-factor enrolments, its audit log, its licence, its TLS certificate and its commit history.
The important sentence first. The configuration file is not a backup. The
Download config button in the UI exports config.boot and nothing else. A restore from
that file produces a working router that nobody can log in to, because every account
lives outside it.
Before you start#
- Shell access to the router — the console, or SSH. SSH is not part of the default configuration; see Turn SSH on, and restrict it, or do this at the console with a USB stick.
- Somewhere to keep the result that has the same access control as the router itself.
- Something to encrypt with:
age,gpg, or your backup system's own encryption.
What a backup has to contain#
All of /config. That is the whole answer, and it is why the recipe is a single tar.
| Path | What is lost without it |
|---|---|
/config/config.boot | The router's entire configuration: interfaces, firewall, NAT, DHCP, DNS, routing, containers. Also the VyOS API key the agent authenticates with, under service https api keys. |
/config/wheelhouse/state.json | Every account and its Argon2id password hash, TOTP secrets, API token hashes, live sessions, agent settings and the licence key. Mode 0600 — it is a credential file. |
/config/wheelhouse/audit.jsonl | The audit log: who changed what, from where, with which role, plus audit.jsonl.1 after a rotation. It moved out of state.json into its own file; entries in an older state.json are migrated into it at start-up. |
/config/wheelhouse/api-key | The agent's copy of that same VyOS API key. It must match what config.boot holds, or the agent cannot read the router. |
/config/wheelhouse/agent.crt, agent.key | The TLS certificate. Regenerated automatically if absent, at the cost of a fresh browser warning for everyone. |
/config/wheelhouse/license-key | The licence, when it was placed as a file rather than pasted into the UI. Pasted keys live in state.json. |
/config/wheelhouse/agent.yaml | The desired-state file, if you use the reconcile loop. |
/config/archive/ | VyOS' numbered configuration revisions — the history behind the Restore buttons on the System page. Not needed to run; needed to go back. |
/config/scripts, /config/auth, /config/user-data | Anything you or VyOS put there: task scripts, PKI material, keys. |
Step 1 — Take it#
From the console, or over SSH as vyos:
sudo tar -czf /tmp/wheelhouse-$(hostname)-$(date +%F).tgz -C / configThen copy it off and remove it from the router:
scp wheelhouse@<router>:/tmp/wheelhouse-*.tgz .
ssh wheelhouse@<router> 'sudo rm -f /tmp/wheelhouse-*.tgz'Or in one step, when sudo on the router does not prompt:
ssh wheelhouse@<router> 'sudo tar -czf - -C / config' > wheelhouse-$(date +%F).tgzStep 2 — Encrypt it#
age -p -o wheelhouse-2026-09-02.tgz.age wheelhouse-2026-09-02.tgz
# or
gpg --symmetric --cipher-algo AES256 wheelhouse-2026-09-02.tgzThen remove the plaintext archive.
Step 3 — On a schedule#
There is no scheduler in Wheelhouse and no off-box backup target — no SFTP, no S3, no git. Run the same command from cron on another machine, so the schedule does not live on the thing you are protecting:
# 03:15 daily
15 3 * * * ssh wheelhouse@edge 'sudo tar -czf - -C / config' | age -r <recipient> > /backups/edge-$(date +\%F).tgz.ageTake one before every upgrade and before every large configuration change, on top of whatever runs nightly.
Check it worked#
A backup you have never opened is a hypothesis. Two checks, in increasing order of value.
Every time — look for the three files that matter:
tar -tzf wheelhouse-2026-09-02.tgz | grep -E 'config/(config.boot|wheelhouse/state.json|wheelhouse/api-key)$'Three lines means you have a backup. Fewer means you have a file.
Once, before you need it — restore onto a VM. Follow Rebuild a dead router onto a new box against a throwaway machine and sign in with your real admin account. That is the only check that proves the whole chain.
The configuration on its own#
Useful for diffing, for version control, and for
--check-against. It is not a substitute for
the tarball.
- From the UI: the Download config button in the System page header. Saves
<hostname>-config.boot. Admin only, because the file is unredacted. From the API:
bashR=https://<router>:8443 T=wh_... curl -sk -H "Authorization: Bearer $T" "$R/api/config/raw" \ | python3 -c 'import json,sys; sys.stdout.write(json.load(sys.stdin)["config"])' > config.boot- As
setcommands, which is easier to read in a diff and the shape the installer and the importer consume:bashcurl -sk -H "Authorization: Bearer $T" "$R/api/config/commands"Below the admin role this response is redacted: private keys, pre-shared secrets and password hashes are blanked.
- On the router:
POST /api/config/savewith{"file": "/config/backup.boot"}writes a copy on the box. That is a snapshot, not a backup — it is on the disk you are protecting against.
What is missing#
Stated plainly, because you will find out anyway:
- No one-click backup. The UI exports
config.bootand nothing else. There is no archive ofconfig.bootplusstate.json, and no support bundle. - No restore-from-file in the product. No upload endpoint, no file picker. Every
restore from off the box goes through
scpand a shell — Restore a configuration. - No scheduled or off-box backup. No SFTP, S3 or git target, no timer.
- No factory reset. There is no command and no UI action that wipes accounts and returns the box to a shipped configuration. Starting over means reinstalling from the ISO.
- No self-service licence release when a router is retired — Rebuild a dead router explains what that costs.
Undoing it#
Delete the archive, securely. shred -u on Linux, or whatever your storage offers.
See also#
- Restore a configuration
- Rebuild a dead router onto a new box
- Undo the last commit — for a change, rather than a disaster
- Upgrade the whole system — take one of these first, every time
- System — history — the revisions the archive holds
Checked against docs/backup-restore.md ·
agent/store.go ·
agent/license.go ·
docs/security.md