Deploy VTA appliance
By the end of this guide, you will have a VTA deployed through Affinidi Portal, with PNM connected to it. Affinidi hosts and operates the VTA in one of two modes, compared in Which mode to choose:
- Shared: runs on a shared network. Evaluation and development purposes only. Offered on every plan, and the only mode on the free Essential and Community plans.
- Isolated: runs in an AWS Nitro Enclave, a Trusted Execution Environment (TEE). Hardware-rooted attestation, keys and data. Offered on the Standard and Enterprise plans.
Already have a running VTA? Skip to Create your first context. To run the VTA yourself, see Self-hosted (open source).
Prerequisites
- An Affinidi Portal account with access to the target project.
- A project with no VTA configuration yet. Each project holds one.
- Rust 1.95 or later, to compile the PNM CLI.
Step 1. Install the PNM CLI
The Personal Network Manager (PNM) CLI is how you connect to and manage your VTA. Both modes use the same version:
cargo install pnm-cli@0.45.0 --locked --registry crates-ioRun pnm help to confirm it installed. Then follow the steps for your mode below, starting at Step 2.
Deploy in Isolated mode
The Isolated appliance type is available on the Standard and Enterprise plans, which are custom plans. To use it, contact Affinidi. On the free Essential and Community plans, deploy in Shared mode instead.
You create a one-time bootstrap claim DID with PNM and give it to Affinidi Portal when you create the VTA. PNM then bootstraps against the VTA: it checks the enclave’s attestation against the measurement you pin, and only if they match does it create your Administrator DID on your machine and hand the VTA’s first administrator role over to it.
Step 2. Create the VTA in Isolated mode
Create a bootstrap claim DID
pnm bootstrap claim-did createCopy the did:key it prints: you enter it in Affinidi Portal, and pass it to PNM in Step 3. PNM keeps its private key in your OS keyring, so run Step 3 on this same machine.
Open the create form
In Affinidi Portal , go to Verifiable Trust Agent and click Create configuration.
Set the configuration fields
Set these fields, and leave the others at their defaults:

Create VTA configuration in Affinidi Portal
| Field | What to enter |
|---|---|
| Appliance type | Isolated, the default on the Standard and Enterprise plans. |
| Bootstrap claim DID | The did:key you created with pnm bootstrap claim-did create. |
| Mediator DID - optional | One of this project’s mediators, or + Enter custom mediator DID. To deploy one, see Deploy an Affinidi-hosted mediator. |
| Appliance size | Enhanced, or Advanced on the Enterprise plan. See which sizes your plan includes. |
Appliance type, Appliance size, and Mediator DID can’t be changed later. Add a mediator now if you want to back up this VTA: backups need DIDComm.
Create the VTA
Click Create, wait for the status to show Complete, then note the VTA DID on the configuration page.

Wait for the deployment to complete.
If your mediator’s Mediator ACL mode is Explicit Allow, add the VTA DID to its allow list: in DIDComm Mediator, open its configuration, click Edit under Access Controls, then Add DID, paste the VTA DID into User’s DID, and click Save. Check the DID first: Portal keeps a saved DID permanently. The default, Explicit Deny, needs no change.
Copy the bootstrap command
Copy it from the Verify the enclave before you bootstrap alert on the configuration page. If you dismissed the alert, click Show bootstrap instructions.

Copy the bootstrap command from the alert below the configuration details.
Step 3. Bootstrap PNM against the VTA
Paste the command you copied, and add --claim-did with the claim DID from Step 2 unless the command already includes it. Add --slug my-vta to give the VTA a short name in PNM, its VTA slug. Run it on the machine where you created the claim DID:
pnm bootstrap connect --vta-did did:webvh:QmVE1TQeCtg3aavpTqasqencJpagRr8JKdGRyoZ5Qx6kRp:your-appliance.vta.affinidi.io --expect-pcr0 8f1d3c5a7b9e...c9a1f3b5 --expect-pcr8 4b2e9d6f0a1c...a7c1e0d2 --claim-did did:key:z6Mk... --slug my-vtaCheck that the output shows (pinned ✓) next to PCR0, followed by Bootstrap complete.. That confirms the enclave runs the build you pinned. The VTA first enrols your claim DID as a temporary administrator. PNM then creates your Administrator DID on this machine, hands the administrator role over to it, and deletes the claim key: the Admin DID line under Bootstrap complete. shows the new DID. For what the pins check, see PCR0 pinning.
Each VTA accepts one bootstrap, so keep --expect-pcr0 in the command. If PNM reports a PCR0 mismatch, treat the VTA as untrusted and replace it, as described in Troubleshooting. If the run stops after TEE attestation verified., run the same command again on the same machine within 24 hours: PNM resumes the hand-over.
Confirm
Whichever mode you deployed, check the connection:
pnm --vta my-vta healthReplace my-vta with your VTA slug. Every check should show ✓. Client DID is the long-lived did:key PNM now authenticates with, not the DID from Step 2. This sample is from a VTA with a mediator:
VTA: my-vta
DID: did:webvh:QmVE1TQeCtg3aavpTqasqencJpagRr8JKdGRyoZ5Qx6kRp:your-appliance.vta.affinidi.io
── VTA ───────────────────────────────────────────
DID did:webvh:QmVE1TQeCtg3aavpTqasqencJpagRr8JKdGRyoZ5Qx6kRp:your-appliance.vta.affinidi.io
✓ resolves (webvh)
Mode DIDComm + REST
URL https://your-appliance.vta.affinidi.io (from DID)
Service ✓ ok
── Authentication ────────────────────────────────
Client DID did:key:z6Mk...
Token ✓ valid (expires in 899s)
── Mediator ──────────────────────────────────────
DID did:webvh:QmSW59XWpXN4xNX5ZJvkYmaJBpBH7d1WN1fR6ZZG8xtjGE:your-appliance.mediator.affinidi.io
✓ resolves (webvh)
✓ pong (14ms)
── VTA DIDComm ───────────────────────────────────
Trust-ping ✓ pong (25ms)The output above is abridged. Between Authentication and Mediator, pnm health also prints a Deployment section with these rows:
- Sealed: whether the VTA is sealed.
- Storage: whether the VTA’s storage is encrypted at rest.
- TSP: whether TSP is enabled.
- TEE: the TEE type and whether it is detected, on a VTA that runs in a TEE.
- Version: the VTA version, shown to administrators only.
- Restored: whether the VTA’s state was restored from a backup, shown to administrators only.
If any check shows ✗, see Troubleshooting below.
PNM treats the first VTA you register on a machine as its default, and --vta picks a specific VTA for a single command. To make this VTA the default for every later command, run pnm vta use my-vta. pnm vta list shows every VTA registered on this machine.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
pnm setup continue fails with no pending VTA named '<name>' | The name differs from the one you gave pnm setup --name in Step 2, or the command is running on a different machine. | Run pnm vta list to see the VTAs on this machine, then run the command on the machine where you ran Step 2, with the same name. |
pnm setup continue prints <vta-did> does not accept <admin-did> yet | The Administrator DID you entered when you created the VTA is not the one PNM printed in Step 2, or --vta-did names a different VTA. | Compare your --vta-did value with the VTA DID on the configuration page. If either DID was wrong, delete the configuration in Affinidi Portal and start again from Step 2 with a new name: the Administrator DID is fixed after creation, and PNM has already saved this VTA. |
pnm setup continue prints Could not reach <vta-did> to check whether it accepts <admin-did> | The VTA has not finished deploying, or its DID cannot be resolved from this machine yet. | Wait until the configuration shows Complete, then run pnm --vta <name> health. PNM completes the connection on its first successful call. |
Could not resolve bootstrap endpoint for VTA DID ... | The VTA’s DID log has not published yet, or this machine cannot reach it. Bootstrap always resolves locally, so a reachable resolver sidecar does not satisfy it: PNM_RESOLVER_URL and [resolver_url] are deliberately ignored for this call. | Retry once the DID log is published and reachable from this machine. Otherwise, run the copied command with --vta-url <vta-url> in place of --vta-did <vta-did>, keeping the PCR values, --claim-did, and --slug. The URL form still pins the enclave through PCR0, but not which VTA answers. |
pnm bootstrap connect fails with PCR0 mismatch: enclave reported …, operator expected … | The pinned PCR0 does not match the enclave’s live attestation, so the running software is not the build you expected. | Treat this VTA as untrusted. Its one-time bootstrap is already used, so delete the configuration in Affinidi Portal, remove the claim key with pnm bootstrap claim-did discard --claim-did <claim-did>, deploy a new one with a new claim DID, and confirm the PCR0 on its configuration page before you bootstrap it. |
pnm bootstrap connect fails with no bootstrap claim key stored for '<vta-slug>' in this machine's OS keyring | The command has no --claim-did, so PNM looked for a claim key stored under the slug. | Add --claim-did <claim-did>, the did:key you entered in Affinidi Portal, and run the command again. |
pnm bootstrap connect fails with no bootstrap claim key for <claim-did> in this machine's OS keyring | The command is running on a different machine from the one where you created the claim DID, --claim-did names a different DID, or bootstrap already succeeded and PNM deleted the claim key. | Run the command on the machine where you created the claim DID, with the did:key you entered in Affinidi Portal. If bootstrap already succeeded, run pnm --vta <vta-slug> health to confirm the connection instead. |
pnm bootstrap connect fails with bootstrap request failed (410 Gone): {"error":"gone: TEE first-boot carve-out has already been used"} | The VTA’s one-time first-boot claim was already used, for example by an earlier run that failed its PCR0 check. | Treat this VTA as untrusted. Delete the configuration in Affinidi Portal, remove the claim key with pnm bootstrap claim-did discard --claim-did <claim-did>, and start again from Step 2 with a new claim DID. |
pnm bootstrap connect fails with the VTA enrolled the claim DID, but handing its entry over to the long-term DID <did> did not complete | The run stopped after the VTA accepted your claim, before PNM finished handing the administrator role to your new Administrator DID. | Run the same pnm bootstrap connect command again on the same machine within 24 hours of the first run. PNM resumes the hand-over. |
pnm bootstrap connect prints The VTA refused this claim before enrolling anything. and fails with 403 Forbidden | The Bootstrap claim DID the VTA was created with is not the DID in --claim-did, or --vta-did names a different VTA. | Compare --claim-did with the Bootstrap claim DID you entered in Affinidi Portal, and --vta-did with the VTA DID on the configuration page. If both match and the VTA still refuses, treat it as untrusted: delete the configuration and start again from Step 2 with a new claim DID. |
pnm bootstrap connect fails with bootstrap request version 1 is not accepted | The installed PNM is older than the version this guide pins, and speaks an earlier first-boot claim format. The one-time bootstrap is not spent. | Install the pinned version from Step 1, then run the command again. |
Create configuration is disabled with Configuration limit reached for this project. Delete a configuration or contact support. | The project already has a VTA configuration. Each project holds one. | Use the existing configuration, or delete it and create a new one. |
pnm health shows ✗ trust-ping failed or ✗ trust-ping timed out on Trust-ping | VTA DIDComm layer has stalled | Restart the VTA with pnm vta restart, then wait for the confirmation VTA is back. Re-run pnm health to confirm Trust-ping shows ✓ pong. |
Next steps
Glad to hear it. Thanks for letting us know.
Sorry to hear that. Thanks for letting us know.
