Skip to main content
RunBook Academy

Proxmox VEXXVII · Multi-Cluster and Multi-TenancySplitting the estate

Cross-cluster migration from the command line

Advanced⏱ ~32 minqmpctpveum

What you'll learn

  • Build a valid target-endpoint string and explain each of its four fields
  • Create a least-privilege receiving token, including the Sys.Incoming privilege and why it exists
  • List what does not travel with a remotely migrated guest and plan for each item
  • Diagnose a remote migration that failed part-way and clean up both ends in the correct order
  • Decide between remote migration and backup/restore for a given guest

Prerequisites

Verified against Proxmox VE 9.2.4 · Proxmox Backup Server 4.2.5 · Ceph Squid / Tentacle · Debian 13 (Trixie) · Linux kernel 7.0 (PVE 9.2 default) · 2026-08-12

Not yet marked complete on this device.

Part XXV covers cross-cluster migration as a Proxmox Datacenter Manager feature: the dialog box, the bandwidth arithmetic, and the duplicate guest the defaults leave behind. This lesson is the layer underneath, which you need for three reasons.

You may not run PDM. You will certainly want this in a script, because a split is a per-guest movement schedule and nobody clicks through two hundred of them. And when a migration fails, the error comes from this layer, not from the one that called it.

The endpoint string, field by field

Everything distinctive about remote migration is in one parameter. Both commands take a <target-endpoint> in this format:

apitoken=PVEAPIToken=user@realm!tokenid=SECRET,host=ADDRESS[,fingerprint=FINGERPRINT][,port=PORT]

Four fields, and each one is a decision:

apitoken — the full PVEAPIToken= header value for a token on the target cluster. Not a password, not an SSH key. The source cluster authenticates to the target as an ordinary API client.

host — an address of any node in the target cluster. There is no cluster VIP concept here; you name a node. If that node is down, the migration does not fail over to a sibling, so pick a node that is healthy at the time and consider which one your script chooses.

fingerprint — optional, and the field most worth caring about. It pins the target’s TLS certificate. Omit it against a cluster using the default self-signed certificate and you are copying disks across a network with no verification of who is receiving them.

port — optional, defaults to the API port, 8006. Present because estates publish the API behind a different port often enough to matter.

Read-only / Safeget the target's certificate fingerprint, on the target
openssl x509 -in /etc/pve/local/pve-ssl.pem -noout -fingerprint -sha256
Read-only / Safe
$ openssl x509 -in /etc/pve/local/pve-ssl.pem -noout -fingerprint -sha256
sha256 Fingerprint=AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99

The receiving token, at least privilege

The token lives on the target cluster and should not be an administrator. Build it from the privileges the operation actually needs.

Configuration changecreate a receiving identity on the TARGET cluster
TARGET_USER="migrate-in@pve"
TOKEN_ID="fromsitea"

pveum user add "$TARGET_USER" --comment "Receives remote migrations from site-a"

pveum role add MigrateReceive --privs "VM.Allocate VM.Config.Disk VM.Config.Network VM.Config.Options VM.Config.HWType VM.Config.CPU VM.Config.Memory VM.Config.Cloudinit VM.PowerMgmt VM.Audit Datastore.AllocateSpace Datastore.Audit Sys.Incoming SDN.Use"

pveum user token add "$TARGET_USER" "$TOKEN_ID" --privsep 1
Configuration changescope the token, on the TARGET cluster
TARGET_USER="migrate-in@pve"
TOKEN_ID="fromsitea"
TOKEN="${TARGET_USER}!${TOKEN_ID}"
DEST_STORAGE="ceph-vm"
DEST_NODE="pve-b1"

pveum acl modify /vms --users "$TARGET_USER" --roles MigrateReceive
pveum acl modify /vms --tokens "$TOKEN" --roles MigrateReceive

pveum acl modify "/storage/$DEST_STORAGE" --users "$TARGET_USER" --roles MigrateReceive
pveum acl modify "/storage/$DEST_STORAGE" --tokens "$TOKEN" --roles MigrateReceive

pveum acl modify "/nodes/$DEST_NODE" --users "$TARGET_USER" --roles MigrateReceive
pveum acl modify "/nodes/$DEST_NODE" --tokens "$TOKEN" --roles MigrateReceive

Running it

Service impact possiblemigrate VM 141 from this cluster to a remote cluster
VMID=141
TARGET_VMID=141
TARGET_HOST="192.0.2.60"
TOKEN_SECRET="REPLACE_ME"
FPR="AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99"

ENDPOINT="apitoken=PVEAPIToken=migrate-in@pve!fromsitea=${TOKEN_SECRET},host=${TARGET_HOST},fingerprint=${FPR}"

qm remote-migrate "$VMID" "$TARGET_VMID" "$ENDPOINT" \
--target-bridge vmbr0:vmbr2 \
--target-storage local-zfs:ceph-vm \
--online \
--bwlimit 200000

The container form is identical in shape:

Service impact possiblethe container equivalent
CTID=205
TARGET_HOST="192.0.2.60"
TOKEN_SECRET="REPLACE_ME"

ENDPOINT="apitoken=PVEAPIToken=migrate-in@pve!fromsitea=${TOKEN_SECRET},host=${TARGET_HOST}"

pct remote-migrate "$CTID" "$CTID" "$ENDPOINT" \
--target-bridge vmbr0:vmbr2 \
--target-storage local-zfs:ceph-ct \
--restart \
--timeout 300

The two mappings are mandatory and their shorthands are covered in Part XXV: a single ID maps everything to it, and the special value 1 maps each source name to itself. The trap is also covered there and is worth repeating in one line: --target-bridge 1 succeeds whenever a same-named bridge exists at the destination, regardless of what network that bridge is attached to.

What does not travel

The guest arrives. A surprising amount of what surrounded it does not, because those things are cluster configuration rather than guest configuration.

Does not travelWhere it livedWhat to do
Backup jobsCluster config, /etc/pve/jobs.cfgCreate a job at the destination and confirm it has run once
PBS backup historyKeyed by guest type and ID on the PBS datastoreDecide whether the old chain is retained, and for how long
Firewall rules at cluster level and security groupsCluster configRecreate; guest-level rules in the guest config do travel
HA resource membership, groups and rulesCluster configRe-add at the destination if the guest needs HA
Pool membershipCluster configRe-add, and re-check the ACLs that depended on it
Replication jobs (pvesr)Cluster configRecreate against a destination peer, or drop
ACLs granting users access to this VMIDCluster configRecreate on the target cluster
Notification matchers referencing the guestCluster configRecreate
Monitoring targets, CMDB entries, DNSOutside PVE entirelyUpdate; nothing will tell you
Storage-level snapshots on the source volumesOn the source storageSee below

That last row deserves its own paragraph. Guest snapshots are a storage feature and the destination storage may not support them in the same way — a guest living on ZFS with a snapshot tree, moved onto an LVM-thin or a directory-backed store, will not arrive with an equivalent tree. Treat a snapshot history as something you deliberately abandon or deliberately preserve elsewhere, and decide before the move rather than discovering it after.

Remote migration or backup and restore?

Both move a guest between clusters. They are not interchangeable.

qm remote-migrateBackup, copy, restore
DowntimeNear-zero with --online, after a long copyFull: restore duration plus start
Total elapsed timeOne disk copyBackup, transfer, restore — often longer
Source left asStopped copy of the guestUntouched and running
RollbackStart the stopped sourceDo nothing; the source never stopped
Depends onAn experimental code path, both clusters healthy and reachableBackups you already take
Works when the source is deadNoYes, if the backup is off-site
Preserves VMIDWith --target-vmid, if freeYes, if free
Scriptable in bulkYesYes

The honest guidance: use remote migration when downtime is the binding constraint and both clusters are healthy, and use backup/restore when correctness and reversibility matter more than minutes. For a planned estate split done over several weekends, backup/restore is frequently the better tool, and it has the enormous advantage of exercising the restore path you claim to have.

Key takeaways

  • The endpoint is apitoken=...,host=...[,fingerprint=...][,port=...]. The fingerprint is optional and pinning it is the difference between an authenticated transfer and a hopeful one. The port defaults to 8006.
  • The receiving token belongs on the target cluster, privilege-separated, and needs Sys.Incoming — a node-level privilege granted on / or /nodes/<node>, not on /vms. Its absence fails the migration late.
  • The credential appears in ps and in shell history. Source it from a file or a secret store, and rotate it when the campaign ends.
  • Bridge and storage maps are mandatory; 1 maps each name to itself and is the silent failure when a same-named bridge is on a different network.
  • Backup jobs, PBS history, cluster firewall rules, HA membership, pool membership, replication jobs, ACLs and monitoring do not travel. Snapshots may not survive a change of storage backend.
  • The transport is the API on 8006, not SSH on 22, and it reuses the pvesm export/import volume machinery — which is why formats convert implicitly.
  • A failed migration has no resume. Stabilise the source, read the destination task log, remove orphaned volumes with pvesm list/pvesm free, then retry.
  • For a planned split, backup and restore is often the better tool: slower, but reversible, non-experimental, and it exercises your restore path.

Knowledge check

Knowledge check · 5 questions

  1. Q1. A receiving token on the target cluster has VM.Allocate, VM.Config.Disk and Datastore.AllocateSpace granted on /vms and on the destination storage. The first remote migration authenticates, begins, and then fails with a permission error some way in. What is missing?

  2. Q2. A VM is remotely migrated from cluster A to cluster B and arrives working. Which of these are left behind because they are cluster configuration rather than guest configuration? Select all that apply.

  3. Q3. A 3 TB remote migration fails at roughly 80% when the inter-site link drops. What is the correct first action?

  4. Q4. Remote migration authenticates to the target cluster API, so the firewall opening it needs between two estates is the API port rather than SSH on port 22 — and the datacenter.cfg migration type setting does not apply to it.

  5. Q5. An estate is splitting over several weekends and 180 guests must move. Downtime of an hour per guest is acceptable. What is the better default tool, and why?

Passing score: 75%. Answers are checked in this browser.