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 agent at a point in time, as a single encrypted .vtabak file: its master seed, keys, identities, contexts, access control configuration, vault, credentials, and most of its other state. For what it leaves out, see What is backed up. 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. A backup restores across plain, hardened, and Nitro Enclave deployments. A VTA in Isolated mode is a Nitro Enclave deployment: it runs in an AWS Nitro Enclave, a Trusted Execution Environment (TEE), with hardware-rooted attestation, keys and data. 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.- A DIDComm or Trust Spanning Protocol (TSP) connection to the VTA. The VTA refuses backup export and import over REST, because the backup password would be readable wherever TLS terminates. To add DIDComm to a self-hosted VTA, see Connect to a DIDComm mediator.
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 | Sessions |
| Keys (derived and imported) and contexts | Caches |
| ACL entries | In-flight backup transfers |
| Vault: third-party secrets and the credentials the VTA holds | In-progress passkey enrolment ceremonies |
| Issued credentials | Messaging outbox |
| DID templates | Idempotency records |
| WebVH DID records and logs | Trust Spanning Protocol (TSP) relationships |
| Imported secret material (KEK-wrapped) | Provider secrets of external accounts |
| Consent records, approvers, and task-consent grants | Trust Task push records |
| Agent memory and application state | The deployment’s own boot material |
| Holder identity (persona) | Internal keys (see Recovery boundary) |
| Operator security policy | |
| Data room group state and consumed room invitations | |
| External account records | |
| Service state | |
Audit log keys, and the audit trail itself with --include-audit |
After a restore, sessions, caches, idempotency records, and TSP relationships are re-established on their own. In-flight backup transfers, passkey enrolments, and queued outbound messages are discarded, so start them again. A restored external account asks for its provider secret again.
Recovery boundary
A backup is how you recover a VTA as a whole, so take one before you depend on the VTA, and again after changes you need to keep. On an Affinidi-hosted VTA, in Isolated or Shared mode, a backup is the only recovery path: you have no access to the host, and no command returns the VTA’s master seed to you outside a backup.
Two things stay outside every backup, so plan for them separately:
- Your administrators’ private keys. A backup holds the access control list (ACL), which names each admin’s DID, but each private key stays with its admin, in that admin’s PNM keyring. If the only super admin’s key is lost, a restore brings back an entry nobody can use. Grant a second super admin, held by another person or stored offline, with
pnm acl create --did <did> --role admin --label <label>and no--contexts. See Roles. - Internal keys. A key created as internal through the VTA SDK is generated on the VTA, separately from the master seed, and stays on that VTA. Its material is lost if you lose the VTA, and it stays behind when you restore onto a different VTA, for example in a migration: the record comes back, but it can’t sign there. A restore onto the VTA that took the backup keeps it. The import message lists internal keys as not in the backup and lost in both cases;
pnm healthafter the restart shows which ones were actually lost.
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.20.0
Audit: false
Backup password (min 15 chars): [hidden]
Validating backup (trust-task descriptor flow, chunked over DIDComm)...
sent chunk 1/1 (12.0 KiB / 12.0 KiB)
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,
pnm health, run as an admin, reports the restore on theRestoredline of its Deployment section: when it was applied and who staged it, the source DID and deployment kind, and anything that did not come back, such as internal keys or hosted DIDs. - 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>. - A self-hosted VTA, or a VTA in Shared mode, takes its public URL and mediator from the backup. A VTA in Isolated mode keeps the public URL and mediator it was created with. When you restore into a new Affinidi-hosted VTA, in Isolated or Shared mode, contact Affinidi support before you rely on the VTA. In both modes, the restored DID document advertises the backup’s public URL and mediator, so the new appliance must be reachable there.
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.20.0
Audit: false
Backup password (min 15 chars): [hidden]
Validating backup (trust-task descriptor flow, chunked over DIDComm)...
sent chunk 1/1 (12.0 KiB / 12.0 KiB)
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.
Test 4: pnm health after an import returns the restore record
Once the VTA is back from the restart that applies an import, run this as an admin:
pnm healthThe Deployment section shows the restore on its Restored line:
Restored yes — applied <applied-at>, staged by <admin-did>
from did:webvh:...
hardened → teeThe second line is the source DID, and the third the source and target deployment kinds: plain, hardened, or tee for a Nitro Enclave. Anything that did not come back follows on its own line, as lost internal key <key-id>, or as detached <did> with the command to re-register that DID. Restored no means the VTA has no record of a restore: check that the import finished and the VTA restarted.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
Export or import fails with refused over a hop-by-hop transport | pnm reached the VTA over REST, which carries no end-to-end encryption for the backup password. | Connect the VTA to a DIDComm mediator so pnm can use DIDComm or TSP, then run the command again. |
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. | Issue each affected service a new credential with pnm auth-credential create --recipient, then open it on the service’s machine with pnm bootstrap open. See Rotate an application credential. |
Next steps
Glad to hear it. Thanks for letting us know.
Sorry to hear that. Thanks for letting us know.