Proxmox: move VMs between two clusters
In short. Three ways exist:
qm remote-migrate, backup and restore, or export the disk by hand. Remote migration is experimental, needs an API token and direct reachability, and cannot move a ZFS disk onto Ceph. Backup and restore uses the replica you already pay for: add the replica namespace as a read-only PBS storage on the destination cluster, restore with a new VMID, fix the MAC and address, cut over, verify.
Goal: move a VM from cluster A to cluster B without a storage migration that may or may not work
A tenant is moving sites, or a cluster is being retired. The VM lives on RBD at site A and must come up on RBD at site B. Proxmox VE 9.2 with Ceph Squid 19.2 on both ends, a PBS per site, and site B's PBS already pulling site A's datastore.
What you need
- Both clusters on PVE 9.2 and a PBS 4.x at each site.
- Site B's PBS holding a replica of site A's datastore in a namespace named for the source (
site-ahere). - A PBS user or API token on PBS B with
DatastoreReaderon that namespace. Read-only, so a mistake on cluster B cannot prune site A's history. - A free VMID on cluster B (
pvesh get /cluster/nextid), and the host plan entry for the guest at its new site, because the address and the MAC both change.
Steps
-
Choose the path. Use
qm remote-migrateonly when both clusters are on the same storage type, the API of cluster B is reachable from cluster A's nodes, and you are happy debugging an experimental feature. It needs an API token on the target in the endpoint string,--target-bridgeand--target-storage, and it refused a ZFS-to-Ceph move outright in my case. For everything else, continue. -
On PBS B, create the read-only identity for cluster B's restore storage. An API token scoped to the namespace is cleaner than a user password.
proxmox-backup-manager user create restore@pbs
proxmox-backup-manager user generate-token restore@pbs cluster-b
proxmox-backup-manager acl update /datastore/store/site-a \
--auth-id 'restore@pbs!cluster-b' --role DatastoreReader
- On cluster B, add the storage entry pointing at the replica namespace. Do this in the web UI or with
pvesm add pbs; either way the secret lands in/etc/pve/priv/storage/pbs-replica.pw, not in your shell history. The namespace is the important part.
pvesm add pbs pbs-replica --server 10.2.5.49 --datastore store --namespace site-a \
--username 'restore@pbs!cluster-b' --fingerprint <pbs-b-fingerprint> --content backup
pvesm list pbs-replica --vmid 100
- Pick the new VMID and restore onto RBD. Leave
--uniqueoff, because you are about to set the MAC deliberately.
NEWID=$(pvesh get /cluster/nextid)
qmrestore pbs-replica:backup/vm/100/2026-08-20T16:00:00Z "$NEWID" --storage rbd-guests
- Give it the new site's MAC and keep the NIC down until you have checked the guest, so it cannot fight the original for an address.
qm set "$NEWID" --net0 virtio=BC:24:11:65:02:0B,bridge=vmbr0,tag=101,link_down=1
qm set "$NEWID" --ipconfig0 ip=10.2.101.11/24,gw=10.2.101.254 --nameserver 10.2.101.2
If the guest was not built with cloud-init, boot it with the link down and change the address inside through qm terminal.
- Cut over. Stop the original, bring the link up on the copy, update the record in the tenant's DNS at
.2and the entry in NetBox, and enrol it in HA.
ssh pve01.site-a qm stop 100
qm set "$NEWID" --net0 virtio=BC:24:11:65:02:0B,bridge=vmbr0,tag=101
qm start "$NEWID"
ha-manager add "vm:$NEWID" --state started
sequenceDiagram
participant A as Cluster A
participant PA as PBS A
participant PB as PBS B
participant B as Cluster B
A->>PA: nightly vzdump, namespace per tenant
PB->>PA: pull sync, read-only token
B->>PB: pbs-replica storage, DatastoreReader on site-a
B->>B: qmrestore to new VMID on RBD
B->>B: new MAC, new address, link up
A-->>A: qm stop 100Verify it worked
qm status "$NEWID"
qm guest cmd "$NEWID" network-get-interfaces | grep '"ip-address"'
dig +short web01.t1.siteb.internal @10.2.101.2
Then the application check from a client, not from the node. A VM that answers ping and serves the wrong database is still a failed move. Once a day has passed, remove the original from HA on cluster A and delete it, and let its backups age out under site A's retention.
Gotchas
- Guest IDs collide across clusters. VM 100 at site A and VM 100 at site B are unrelated machines, so the replica lives in a per-source namespace and the restore always takes a fresh ID.
- A read-only storage entry cannot prune, verify or write, and cluster B will log that it cannot run its backup job against it. That is correct; do not add backup permissions to make the warning go away.
- Restoring with the original MAC and VLAN onto a VLAN that spans sites puts two machines with one MAC on one L2. The derived MAC includes the site byte for exactly this reason.
qm remote-migratewith a container ispct remote-migrate, and it has the same constraints plus the bind-mount rules.- Snapshots do not travel. A PBS backup is a point-in-time image; the restored guest has no snapshot history.
FAQ
Why not just copy the RBD image with rbd export and import? It works and it is fast on a 10G link. It also needs cluster-to-cluster Ceph reachability, a hand-built VM config on the far side, and nothing checks the copy. The backup path is already verified by PBS and already at the other site.
Can I restore from site A's PBS directly instead of the replica? Yes, if cluster B can reach PBS A over the mesh VPN and you add a storage entry for it. The replica is local, faster, and it is the thing you need to prove you can restore from anyway.
What about the IP inside a non-cloud-init guest?
Boot with link_down=1, open the serial or VNC console, change the address and DNS, then bring the link up. Slower than cloud-init, which is the argument for building templates with it.