Proxmox VE Disaster Recovery with FlashArray, Part 1: Protect and Prepare
Proxmox doesn't ship with anything like VCF Live Recovery, so when someone asks how you'd bring VMs back at a second site, the answer is mostly array replication and a few small text files
Proxmox VE Disaster Recovery with FlashArray, part 1 of 3.
So you've got Proxmox VE running on FlashArray, and someone has asked the question every virtualization team gets eventually: what happens if we lose the site?
If you came from VMware, the reflex answer is VCF Live Recovery (the artist formerly known as Site Recovery Manager). Proxmox doesn't have one. What it does have is a storage layer that is just Linux: iSCSI, multipath and LVM. That turns out to be a pretty good fit for array replication, because the array can move the data and everything Proxmox needs to bring a VM back is either on that data or in a small text file.
This series walks through recovering a set of VMs at a recovery site from FlashArray asynchronous replication. No automation, no orchestration product. Just the commands, in order, with the output you should expect to see. I ran the whole thing end to end on Proxmox VE 9.2 and Purity//FA 6.12: a DR test that leaves production running, a cleanup, a second test, and then a planned failover. It ended up too long for one post, so it's in three. This first one sets everything up: what the array replicates and what it doesn't, protecting the VMs, and getting the rest of what the DR site needs over there. Part two prepares the DR site and runs the DR test, and part three is the failover and what to do when IDs collide.
What replication covers, and what it doesn't
Before running a single command, it helps to be clear about what actually lands at the DR site.
| Item | Where it lives | Replicated by the array? |
|---|---|---|
| VM disks | LVs in a volume group on a FlashArray volume | Yes |
| LVM metadata (VG and LV names) | On the same volume | Yes |
VM configuration (/etc/pve/qemu-server/<vmid>.conf) | The Proxmox cluster file system on the source nodes | No |
Storage definitions (/etc/pve/storage.cfg) | Same | No |
| Bridges and VLANs | /etc/network/interfaces on each node | No |
| ISO images, cloud-init snippets, hookscripts | Usually local storage | No |
The top two rows are the array's job. The bottom four are yours. The good news is that a VM config is a short text file, and once the disks are visible at the DR site, recovering a VM is mostly a matter of putting that file back in the right place with the right bridge name.
The example environment
| Production | DR | |
|---|---|---|
| Proxmox VE | Two-node cluster pve-prod-01, pve-prod-02 | pve-dr-01 |
| FlashArray | fa-prod | fa-dr |
| Protocol | iSCSI, shared LVM | iSCSI, shared LVM |
| VM network bridge | vlan100 | vmbr0 |
fa-prod already has an array connection to fa-dr for replication. Both Proxmox sites are already set up for iSCSI and multipath per the Proxmox VE iSCSI quick start, and pve-dr-01 already has iSCSI sessions to fa-dr.
Three VMs make up the "application" we want to protect:
| VMID | Name | Disks |
|---|---|---|
| 801 | dr-web-01 | scsi0 on pve-dr-lvm01 |
| 802 | dr-db-01 | scsi0 on pve-dr-lvm01, scsi1 (data) on pve-dr-lvm02 |
| 803 | dr-app-01 | scsi0 on pve-dr-lvm02 |
dr-db-01 has its OS disk on one FlashArray volume and its data disk on another. I did that on purpose. It is the case where replicating volumes one at a time goes wrong, and it is why everything below uses a protection group.
Protect the VMs at the source
Lay out the storage
Each FlashArray volume carries one LVM volume group, and each volume group is one Proxmox storage:
| FlashArray volume | Volume group | Proxmox storage |
|---|---|---|
pve-dr-demo-vol-01 | pve-dr-vg01 | pve-dr-lvm01 |
pve-dr-demo-vol-02 | pve-dr-vg02 | pve-dr-lvm02 |
The rule of thumb is simple: the volume is the unit of replication, so put VMs that need to fail over together on the same set of volumes, and put those volumes in the same protection group. If another team's VMs share your volume group, they come along for the ride every time you recover.
Record which volume holds which VMs
At recovery time you'll be looking at volumes on one system and volume groups on another, so write the mapping down while production is healthy. Production may not be there to ask when you need the answer, so this record is only useful if a copy already lives at the DR site, next to the config copies. The link between the two is the WWID: for a FlashArray volume, the Linux WWID is 3624a9370 followed by the volume's serial number in lowercase. This script prints each FlashArray-backed PV with its serial, volume group and the VMIDs that have disks on it. Save it on each production node as /usr/local/sbin/pve-dr-inventory and make it executable (chmod +x), because you'll run it again every time you send copies to the DR site:
#!/bin/bash
# FlashArray serial -> volume group -> VMIDs on this node, for the DR site.
echo "# $(hostname) $(date -u +%FT%TZ)"
for pv in $(pvs --noheadings -o pv_name | grep -o '/dev/mapper/3624a9370[0-9a-f]*'); do
vg=$(pvs --noheadings -o vg_name "$pv" | tr -d ' '); [ -n "$vg" ] || continue
serial=$(basename "$pv" | sed 's/^3624a9370//' | tr a-f A-F)
vms=$(lvs --noheadings -o lv_name "$vg" | grep -o 'vm-[0-9]*' | sort -u | tr '\n' ' ')
echo "serial=$serial vg=$vg vms: $vms"
done# pve-dr-inventory
# pve-prod-01 2026-11-02T20:07:05Z
serial=A1B2C3D4E5F6A7B800000001 vg=pve-dr-vg01 vms: vm-801 vm-802
serial=A1B2C3D4E5F6A7B800000002 vg=pve-dr-vg02 vms: vm-802 vm-803
It lists every FlashArray-backed volume group the node can see, not only the protected ones, which is fine. With shared LVM every node sees the same volume groups. If some of your storage is restricted to particular nodes, run it on each of those nodes too.
Match the serials against the array:
fa-prod> purevol list pve-dr-demo-vol-01 pve-dr-demo-vol-02
Name Size Source Created Serial
pve-dr-demo-vol-01 100G - 2026-11-02 10:47:45 PST A1B2C3D4E5F6A7B800000001
pve-dr-demo-vol-02 100G - 2026-11-02 10:47:54 PST A1B2C3D4E5F6A7B800000002
Now you have the whole chain: FlashArray volume, serial, volume group, Proxmox storage ID, VMs. It goes to the DR site along with the configs, a little further down. Naming each volume group after its FlashArray volume makes the chain readable without the lookup, but the serial is the part that can't be wrong.
If production is gone and this record never made it across, most of the chain can be rebuilt from the DR site alone. Step 4 of the DR test, in part two, shows how. The one link that can't be rebuilt is which storage ID goes with which volume group, and that comes from production's storage.cfg, which is the next thing to copy.
Create the protection group
On fa-prod, create a protection group with both volumes and fa-dr as the target:
fa-prod> purepgroup create --vollist pve-dr-demo-vol-01,pve-dr-demo-vol-02 --targetlist fa-dr pve-dr-demo-pg
Name Source Targets Host Groups Hosts Volumes
pve-dr-demo-pg fa-prod fa-dr - - pve-dr-demo-vol-01
pve-dr-demo-vol-02
A protection group snapshot captures every member volume at the same point in time. That's what makes dr-db-01 safe: its OS disk and its data disk come back from the same instant, not from two snapshots taken a few seconds apart.
Now give it a replication schedule and decide how long the DR array keeps replicas. This replicates hourly, keeps every replica for a day, and then keeps four per day for a week:
fa-prod> purepgroup schedule --replicate-frequency 1h pve-dr-demo-pg
fa-prod> purepgroup retain --target-all-for 1d --target-per-period 4 --target-period-length 1d --target-days 7 pve-dr-demo-pg
fa-prod> purepgroup enable --replicate pve-dr-demo-pg
fa-prod> purepgroup list --schedule pve-dr-demo-pg
Name Schedule Enabled Frequency At Blackout
pve-dr-demo-pg snap False 1h - -
replicate True 1h - -
The replication frequency is your recovery point objective. If you replicate hourly, you can lose up to an hour of writes in an unplanned failover. Pick the number with the application owner, not for them.
You can also take a snapshot and replicate it right now, which is what I use for DR tests and for the last step before a planned failover:
fa-prod> purepgroup snap --replicate-now --suffix drtest1 pve-dr-demo-pg
Name Source Created
pve-dr-demo-pg.drtest1 pve-dr-demo-pg 2026-11-02 10:56:28 PST
One thing to know about on-demand snapshots: unless you add --apply-retention, Purity keeps them until you destroy them. That's handy for a test you want to repeat. It's less handy six months later when someone asks where the space went.
Send the DR site what the array doesn't replicate
The array replicates the disks. Three more things have to reach the DR site some other way, and all three are small text files:
| What | Why the DR site needs it |
|---|---|
VM configs, /etc/pve/nodes/*/qemu-server/*.conf | Without them there is nothing to start: no CPU, memory, NICs, or list of disks. |
Storage definitions, /etc/pve/storage.cfg | The only record of which storage ID maps to which volume group. The configs reference disks by storage ID. Look at dr-db-01: it has a vm-802-disk-0 in both volume groups, so the LV names alone can't tell you which one is pve-dr-lvm01:vm-802-disk-0. |
The inventory from pve-dr-inventory | The serial → volume group → VMs chain from the previous section, so you can check that you've connected the right volumes. |
Do it by hand
From any production node that can SSH to the DR node, run these three commands:
# 1. VM configs from every node in the cluster
rsync -a /etc/pve/nodes/*/qemu-server/ root@pve-dr-01:/root/dr-configs/
# 2. Production's storage definitions, under a name that can't be mistaken for the DR cluster's own
rsync -a /etc/pve/storage.cfg root@pve-dr-01:/root/dr-configs/storage.cfg.prod
# 3. The volume inventory
/usr/local/sbin/pve-dr-inventory | ssh root@pve-dr-01 'cat > /root/dr-configs/inventory.prod'
rsync is installed on Proxmox VE by default. If you prefer scp, it works just as well. Make sure /root/dr-configs exists on the DR node before the first run (ssh root@pve-dr-01 mkdir -p /root/dr-configs).
A couple of things in there are on purpose. I read /etc/pve/nodes/*/qemu-server/ rather than /etc/pve/qemu-server, because the short path is a link to whichever node you happen to be logged in to, and in a two-node cluster that's only half your VMs. The longer path pulls both halves into one directory. VMIDs are unique across the cluster, so nothing overwrites anything, and since /etc/pve is shared it doesn't matter which production node you run it from.
The files land in a plain directory on the DR node. Not in /etc/pve over there. Drop them into /etc/pve on the DR side and you've just registered production's VMs on the DR cluster, and production's storage.cfg would replace the DR cluster's own. I also left off --delete. If someone removes a VM in production, its config sticks around in the DR copy until somebody cleans it up, and I'd rather have a stale file than a missing one.
Check what arrived:
# ls /root/dr-configs
801.conf 802.conf 803.conf inventory.prod storage.cfg.prod
# grep -A2 "pve-dr-lvm0" /root/dr-configs/storage.cfg.prod
lvm: pve-dr-lvm01
vgname pve-dr-vg01
content images,rootdir
--
lvm: pve-dr-lvm02
vgname pve-dr-vg02
content rootdir,images
These copies are only as current as the last time you sent them. If someone adds a disk to dr-db-01 and nobody resends, the DR site gets a VM with a disk missing from its config. The new LV is on the replicated volume. The config just doesn't know about it. At a minimum, rerun the three commands after any change to a protected VM's hardware or to the storage layout, and before every DR test.
Or let cron do it
The same three commands, in a script on a production node, keep the copies current without anyone remembering. Save this as /usr/local/sbin/pve-dr-sync and make it executable:
#!/bin/bash
set -e
rsync -a /etc/pve/nodes/*/qemu-server/ root@pve-dr-01:/root/dr-configs/
rsync -a /etc/pve/storage.cfg root@pve-dr-01:/root/dr-configs/storage.cfg.prod
/usr/local/sbin/pve-dr-inventory | ssh root@pve-dr-01 'cat > /root/dr-configs/inventory.prod'
Then schedule it in /etc/cron.d/pve-dr-sync:
*/15 * * * * root /usr/local/sbin/pve-dr-sync
The node running the job needs SSH key access to the DR node. Install the script on more than one production node if you want the job to keep running when a node is down. Running it twice is harmless. With a 15-minute schedule, the copies can be up to 15 minutes behind a change, which is usually far less than the replication interval anyway.
Either way, the DR copies of these files only exist because production sent them. That's why the cron job or the manual routine has to run before you need it. On the day production is gone, there's nothing left to copy from.
Here is what one of them looks like:
# cat /root/dr-configs/802.conf
agent: 1
boot: order=scsi0
cicustom: user=local:snippets/pve-dr-demo-user.yaml
cores: 2
cpu: x86-64-v2-AES
ide2: pve-dr-lvm01:vm-802-cloudinit,media=cdrom
ipconfig0: ip=dhcp
memory: 2048
name: dr-db-01
net0: virtio=BC:24:11:00:08:02,bridge=vlan100
ostype: l26
scsi0: pve-dr-lvm01:vm-802-disk-0,iothread=1,size=10G
scsi1: pve-dr-lvm02:vm-802-disk-0,iothread=1,size=20G
scsihw: virtio-scsi-single
serial0: socket
smbios1: uuid=6f1e2d3c-4b5a-4968-8776-a5b4c3d2e1f0
vga: serial0
vmgenid: 0a9b8c7d-6e5f-4a3b-9c2d-1e0f9a8b7c6d
Read it with DR eyes. The disks reference storage IDs, so if you recreate the same storage IDs at the DR site, those lines work unchanged. Three lines will not work:
bridge=vlan100names a bridge that doesn't exist onpve-dr-01.cicustompoints atlocal:snippets, which isn't replicated.ide2is a cloud-init drive. Proxmox regenerates it at boot, and it would need that snippet. If you change the cloud-init settings, the generated instance ID changes too, and cloud-init treats the VM as brand new on first boot. For a recovered VM, I would rather remove the drive.
Same story for an ISO left in a virtual CD-ROM (ide2: local:iso/...), or a hookscript sitting on local storage. Any one of these keeps the VM from starting at the DR site. The error will point you at the config, though, not at the storage.
Also note that dr-db-01 has a vm-802-disk-0 on both storages. Proxmox numbers disks per storage, so that is normal, and it's another reason to keep the storage IDs identical on both sides.
That's the production side done. The VMs replicate on a schedule, and the DR site has copies of everything the array doesn't carry. In part two I get the DR site ready and then use all of it to run a full DR test while production keeps running.