Back up and restore VTA state
By the end of this guide, you have a password-encrypted backup of your VTA’s operator state, and you know how to restore it.
A VTA backup captures the whole agent at a point in time, as a single encrypted .vtabak file: its master seed, keys, identities, contexts, access control configuration, vault, credentials, and the rest of its state. Restoring it replaces all current data in the target VTA. For what the keys inside it are derived from, and why the master seed matters to recovery, see Keys and contexts.
Rebuilding a lost VTA by hand means re-minting every key, re-provisioning every application, and reconstructing the access control list (ACL) from memory, with the original DIDs gone. A backup is what turns that into a restore, onto the same VTA or a new one, and across plain, hardened, and Nitro Enclave deployments. Runtime records such as sessions are re-established after a restore; see What is backed up.
Use this guide when:
- You are about to depend on a VTA and need a recovery position first.
- You are migrating a VTA to a new host.
- You want to clone a VTA environment, for example to seed staging from development.
- You are about to make a change you may need to roll back.
Prerequisites
- A running VTA. See Quickstart.
pnminstalled and connected to the VTA as a super-admin. Exporting and importing a backup are super-admin operations.
Exporting a backup
pnm backup exportYou are prompted to enter and confirm a password (minimum 15 characters). The backup holds the master seed, so anyone with the file and its password can recreate every derived key: use a long, unique password. The backup is saved to a .vtabak file in the current directory:
vta-backup-<did-tail>-<timestamp>.vtabak<did-tail> is the final segment of the VTA’s DID, not the full DID value.
Including audit logs
Audit logs are excluded from the default export because they can be large. Add --include-audit to include them:
pnm backup export --include-auditSpecifying the output path
pnm backup export --output /path/to/my-backup.vtabakWhat is backed up
| Included | Not included |
|---|---|
| Master seed and JWT signing key | Key material of internal keys, created with pnm keys create --internal: their records are restored, their material stays on the VTA that created them |
| Keys (derived and imported) and contexts | Sessions |
| ACL entries | Caches |
| Vault: third-party secrets and the credentials the VTA holds | In-flight backup transfers |
| Issued credentials | In-progress passkey enrolment ceremonies |
| DID templates | Messaging outbox |
| WebVH DID records and logs | Idempotency records |
| Imported secret material (KEK-wrapped) | Trust Spanning Protocol (TSP) relationships |
| Consent records, approvers, and task-consent grants | |
| Agent memory and application state | |
| Holder identity (persona) | |
| Operator security policy | |
| Messaging room groups and invitations | |
| Service state | |
Audit log keys, and the audit trail itself with --include-audit |
Everything in the right-hand column except internal key material is re-established on its own after a restore. Internal keys come back as records only, so they sign only on the VTA that created them. The import result names each one.
Encryption details
Backups are encrypted with Argon2id + AES-256-GCM:
| Parameter | Value |
|---|---|
| KDF | Argon2id |
| Memory cost | 64 MiB (OWASP-recommended profile) |
| Iterations | 3 |
| Parallelism | 4 threads |
| Encryption | AES-256-GCM |
| Salt | 32 bytes, random per export |
The KDF parameters are stored in the backup envelope. On import, the VTA validates that the parameters fall within accepted bounds. Extreme values are rejected to prevent memory exhaustion, which matters most for Nitro Enclave deployments.
Inspecting a backup before importing
Use --preview to see what a backup contains without applying any changes:
pnm backup import my-backup.vtabak --previewOutput:
The preview asks for the backup password, decrypts the backup, and counts its contents:
Backup file: my-backup.vtabak
Source DID: did:webvh:...
Created: 2026-06-18 10:00:00.482913107 UTC
Version: 0.5.3
Audit: false
Backup password (min 15 chars): [hidden]
Validating backup (trust-task descriptor flow, HTTPS stream)...
Keys: 12
ACL entries: 5
Contexts: 3
Audit logs: 0
Preview only — no changes applied.Importing a backup
All existing VTA data, including data added since this backup was generated, will be replaced. The import has no undo. To keep the current state recoverable, export a backup of this VTA before you import.
pnm backup import my-backup.vtabakThe import flow:
- Metadata display: shows source DID, creation time, version, and whether audit logs are included.
- Password prompt: enter the backup password (not confirmed on import).
- Preview: shows counts of keys, ACL entries, contexts, and audit logs to be restored.
- Confirmation: after
WARNING: This will REPLACE ALL DATA in the VTA., typeyesatType 'yes' to confirm:. Any other answer printsImport cancelled.and changes nothing. - Apply: the VTA commits the import, and
pnmprintsThe VTA is restarting to apply the restore.All current data is replaced with the backup contents on that boot.
Compatibility rules
| Condition | Result |
|---|---|
| Restoring a backup of the VTA’s own DID | Accepted |
| Restoring a backup of a different DID, such as onto a freshly set-up VTA during disaster recovery or a migration | Refused, unless you pass --replace-identity |
| Restoring across deployment kinds, between plain, hardened, and Nitro Enclave VTAs | Accepted in any direction |
A freshly set-up VTA already has a DID of its own, so a restore onto it replaces that identity. Run the import with --replace-identity to confirm that is what you want:
pnm backup import my-backup.vtabak --replace-identityThe restored VTA then runs as the backup’s DID.
On a self-hosted target, two more requirements apply:
- Persistent seed store: a plain or hardened VTA must keep its seed in a store that survives a restart, such as the OS keyring, a cloud secret manager, Vault, or Kubernetes. A restore into one that cannot is refused before anything changes.
- Same deployment kind until the restart: keep
[hardened] enabledunchanged, and stay on the same kind of deployment, between the import and the restart that applies it.
After import
- The VTA restarts itself to apply the restore. Once it is back,
GET /health/detailsreports the restore: when it happened, the source DID and deployment kind, and anything that did not come back. - If the VTA DID has changed, re-authenticate with credentials from the restored backup, and inform any integrations of the new VTA DID.
- DIDs the VTA hosted on a DID-hosting server are detached from that server after a
--replace-identityrestore. Re-attach each withpnm did-mgmt dids register --did <did> --server <server-id>.
Confirm
Test 1: the exported file exists and previews cleanly
Run this against the file pnm backup export wrote, before you rely on it for recovery. A backup you have never read back is not yet a backup.
pnm backup import vta-backup-<did-tail>-<timestamp>.vtabak --previewBackup file: vta-backup-<did-tail>-<timestamp>.vtabak
Source DID: did:webvh:...
Created: 2026-06-18 10:00:00.482913107 UTC
Version: 0.5.3
Audit: false
Backup password (min 15 chars): [hidden]
Validating backup (trust-task descriptor flow, HTTPS stream)...
Keys: 12
ACL entries: 5
Contexts: 3
Audit logs: 0
Preview only — no changes applied.Test 2: the counts match the VTA you exported from
Compare the preview counts against the live VTA:
pnm keys list
pnm acl list
pnm contexts listRun as a super-admin, these commands return the same Keys, ACL entries, and Contexts totals as the preview. A context-scoped admin sees only its own contexts, so its counts are lower. If the preview is lower than a super-admin’s listing, the export ran before the most recent changes.
Test 3: the password is the one you recorded
A successful preview in Test 1 already proves the password, because the preview decrypts the backup to count its contents. Record the password somewhere separate from the backup file: there is no password recovery path for VTA backups.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
Import fails with backup vta_did mismatch: … | The target VTA runs as a different DID from the backup source, for example a freshly set-up VTA. | Re-run with pnm backup import <file> --replace-identity if replacing the target’s identity is intended. See Compatibility rules. |
Import fails with incorrect backup password | The password does not match the one used at export. | Re-enter the password recorded at export. A backup whose password is lost cannot be opened, so take a new export from the source VTA if it is still running. |
| A hosted DID stops updating after a restore | A --replace-identity restore detaches hosted DIDs from their DID-hosting server. | Re-attach each with pnm did-mgmt dids register --did <did> --server <server-id>. |
| Service cannot authenticate after restore | Service credentials were minted after the backup was taken. | Re-provision affected services using pnm bootstrap. |
Next steps
Glad to hear it! Please tell us how we can improve more.
Sorry to hear that. Please tell us how we can improve.
Thank you for sharing your feedback so we can improve your experience.