 {"id":348,"date":"2026-08-29T19:34:56","date_gmt":"2026-08-30T00:34:56","guid":{"rendered":"https:\/\/www.networkjack.info\/blog\/?p=348"},"modified":"2026-08-29T19:34:58","modified_gmt":"2026-08-30T00:34:58","slug":"migrating-pvgrub-based-xen-domu-virtual-machines-to-proxmox","status":"publish","type":"post","link":"https:\/\/www.networkjack.info\/blog\/2026\/08\/29\/migrating-pvgrub-based-xen-domu-virtual-machines-to-proxmox\/","title":{"rendered":"Migrating PVGrub-based Xen domU Virtual Machines to ProxMox"},"content":{"rendered":"\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Background<\/h2>\n\n\n\n<p>We have been luddites running plain Xen 4.x hypervisor atop of Debian for way too long. Manually launching\/stopping virtual machines, no live migration, no integrated backups, etc&#8230; So, ProxMox, here come.<\/p>\n\n\n\n<p>One problem.<br>Most all of our Linux-based instances were converted long ago to use the Xen-PVGrub boot subsystem to allow the vm&#8217;s Admin to internally maintain their own kernel. ProxMox doesn&#8217;t really have a facility to import those systems and launch\/them as-is. The SeaBIOS booting system in ProxMox really wants to have a partition map to grab onto. PVGrub based systems have no such partition maps on their boot disk devices.<\/p>\n\n\n\n<p>The solution: slipstream a partition map onto the <em>front<\/em> of a virtual machines boot disk container. Assimilation can be done, but it requires precision of execution and patience.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p><strong>Preflight check:<br><\/strong>1. Create a new tiny VM on the source Xen server called MBRFixer. Its entire purpose will be to run the partition map installation and GRUB updating commands on the target boot disk within a container that is cleanly separated from the dom0 environment. Explicitly install kpartx, fdisk, e2fsprogs, <br>parted, python3. Manually download and install the grub-pc-bin package.<\/p>\n\n\n\n<p>2. Restructure your virtual disks\/content within each VM so that the boot drive is as small as possible. This is to minimize the downtime by reducing the amount of data the block shifting process has to churn through when we go to drop in the partition map in that first 1MB of the block device.<br><br>I&#8217;ve done conversions from a VM that started out with a single 400GB disk. A secondary drive was added to move all the &#8220;data&#8221; to (symlinks are your friend) and then the boot disk is trimmed down to 30-40G in size, with subsequent resize2fs and lvresize operations to finally shrink down that boot container. This is not a requirement, just an optimization to reduce downtime.<br><br>3. Make a backup of the boot disk. If you utilize LVM, simply use the LVM mirroring feature to have a copy made and prepared while the VM is sill running before you shut it down to begin the assimilation process.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Assuming the main extents are on \/dev\/md0 and \n# \/dev\/md1 is another PV in the vm1vg1 Volume Group:\n\nlvconvert  -y -m 1 vm1vg1\/wwwserver1-boot \/dev\/md1<\/code><\/pre>\n\n\n\n<p>Once the mirroring process has sync&#8217;ed up the extents onto both PVs, shutdown the VM and then split the mirror:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>lvconvert  -y --name=wwwserver1-boot-GX --splitmirrors 1  vm1vg1\/wwwserver1-boot<\/code><\/pre>\n\n\n\n<p>4. Start the VM back up and then begin the process of preparing the boot disk with the following:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># shift from grub-xen to grub-pc and add in the QGA.\n# wait until after the vm is assimilated before doing any other upgrades.\n\napt install grub-pc qemu-guest-agent\n\n# explicitly add in virtio modules to the initrd when it gets rebuilt\necho -e \"virtio_pci\\nvirtio_blk\\nvirtio_scsi\\nvirtio_net\" \\\n  >> \/etc\/initramfs-tools\/modules<\/code><\/pre>\n\n\n\n<p>5. Update fstab to target mounts via UUID instead of Xen Virtual Disk device names.<br>First drop them into fstab with blkid:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>blkid >> \/etc\/fstab<\/code><\/pre>\n\n\n\n<p>then edit fstab:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># \/etc\/fstab: static file system information. \n#\n# &lt;file system> &lt;mount point> \u00a0 &lt;type>\u00a0 &lt;options> \u00a0 \u00a0 \u00a0 &lt;dump>\u00a0 &lt;pass>\n\nproc\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \/proc \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 proc\u00a0 \u00a0 defaults\u00a0 \u00a0 \u00a0 \u00a0 0 \u00a0 \u00a0 \u00a0 0\ndevpts\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \/dev\/pts\u00a0 \u00a0 \u00a0 \u00a0 devpts\u00a0 rw,noexec,nosuid,gid=5,mode=620 0\u00a0 0\n\n\/dev\/xvda1\u00a0 \/\u00a0 ext4\u00a0 noatime,nodiratime,discard,errors=remount-ro 0 1\n\n\/swapfile \u00a0 none \u00a0 swap \u00a0 sw \u00a0 0 \u00a0 0\n\n\/dev\/xvda1: UUID=\"<strong>94477222-7621-4ea9-a0a5-cc31ca9feef3<\/strong>\" BLOCK_SIZE=\"4096\" TYPE=\"ext4\"<\/code><\/pre>\n\n\n\n<p>any <em>xvd<\/em> mount needs to be changed to a <em>UUID<\/em> mount. Resulting fstab:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># \/etc\/fstab: static file system information.\n#\n# &lt;file system> &lt;mount point> \u00a0 &lt;type>\u00a0 &lt;options> \u00a0 \u00a0 \u00a0 &lt;dump>\u00a0 &lt;pass>\n\nproc\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \/proc \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 proc\u00a0 \u00a0 defaults\u00a0 \u00a0 \u00a0 \u00a0 0 \u00a0 \u00a0 \u00a0 0\ndevpts\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \/dev\/pts\u00a0 \u00a0 \u00a0 \u00a0 devpts\u00a0 rw,noexec,nosuid,gid=5,mode=620 0\u00a0 0\n\nUUID=94477222-7621-4ea9-a0a5-cc31ca9feef3\u00a0 \/\u00a0 ext4\u00a0 noatime,nodiratime,discard,errors=remount-ro 0 1\n\n\/swapfile \u00a0 none \u00a0 swap \u00a0 sw \u00a0 0 \u00a0 0<\/code><\/pre>\n\n\n\n<p>6. Now rebuild initrd and update grub:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>update-initramfs -u -k all\nupdate-grub<\/code><\/pre>\n\n\n\n<p>7. <strong>Shutdown the VM<\/strong> and attach the boot disk to the MBRFixer virtual machine:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>xl block-attach mbrfixerX2 phy:\/dev\/vm1vg1\/wwwserver1-boot xvdb w<\/code><\/pre>\n\n\n\n<p>you can verify it attached properly with a block-list:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>xl block-list mbrfixerX2<\/code><\/pre>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>Vdev\u00a0 BE\u00a0 handle state evt-ch ring-ref BE-path<br>51712 0 \u00a0 76 \u00a0 \u00a0 4 \u00a0 \u00a0 -1 \u00a0 \u00a0 -1 \u00a0 \u00a0 \u00a0 \/local\/domain\/0\/backend\/vbd\/76\/51712<br><strong>51728<\/strong> 0 \u00a0 76 \u00a0 \u00a0 4 \u00a0 \u00a0 -1 \u00a0 \u00a0 -1 \u00a0 \u00a0 \u00a0 \/local\/domain\/0\/backend\/vbd\/76\/51728<\/p>\n<\/blockquote>\n\n\n\n<p><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p>While this currently PVGrub-configured boot disk is attached to the MBRFixer, the following tasks will be performed <strong>inside<\/strong> that VM: (you can check it attached properly with lsblk)<br><br>8a. fsck the file system:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># fsck.ext4 -f \/dev\/xvdb<\/code><\/pre>\n\n\n\n<p>8b. shrink the file system slightly so there is unused\/disposable space at the end of the device: The device only needs to be shrunk by a single megabyte, but in practice its easier to just subtract a GB from the device size, so if it&#8217;s a 40GB boot drive, resize down to 39G:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># resize2fs -p \/dev\/xvdb 39G<\/code><\/pre>\n\n\n\n<p>If you are working with a file system type that cannot be shrunk, you&#8217;ll need to go the opposite route and <em>expand<\/em> the disk. This doesn&#8217;t change the requirement for shifting the file system forward. Simply tacking another GB onto the end of the existing LV\/disk in order to have space to shift forward into works just as well.<\/p>\n\n\n\n<p>8c. Now that there should be nothing related to the containers file system within the last GB of the block device, I use a simple python script to shift the entire block device forward 1MB, so that we have a space at the beginning to manually insert a partition map. The script starts at the N-1 end of the device (ignoring the last 1MB block), reads each 1MB block then writes it to the block 1 MB forward in the block device. Once the process has completed, whatever was on the block device has now been shifted for by 1MB. The script completes the process by zeroing out the first 1MB block in preparation for the partition map.<br><br><a href=\"\/files\/block_shifter.py\" data-type=\"link\" data-id=\"\/files\/block_shifter.py\">block_shifter.py<\/a><\/p>\n\n\n\n<p>sha384: <br>Etb88BHnMd0HsWddhNL2bMvLaQVaTaSLl9zYm7fU8ZdTLydxNNhJnL7rYyp40dYk<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># python3 block_shifter.py \/dev\/xvdb\nDevice : \/dev\/xvdb\nSize   : 6,442,450,944 bytes  (6144 x 1024 KiB chunks)\nShifting chunks 0..6142 ? 1..6143\n\n  &#91;100.0%]  chunk        0 ? 1       \nZeroing chunk 0 \u2026\nDone.\n<\/code><\/pre>\n\n\n\n<p>8d. While that block shifting is going on, take this time to go into the ProxMox admin interface and create the new VM. I set the same vcpus and memory settings and copy over the MAC addresses into the Network Device(s) from the xen.cfg file:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#\n#  Kernel + memory size\n#\nkernel = \"\/usr\/lib\/grub-xen\/grub-x86_64-xen.bin\"\n\nvcpus       = '2'\nmemory      = '2048'\npool        = 'Pool-U3'\ncpu_weight  = 384\n\n#\n#  Disk device(s).\ndisk        = &#91;   \n                  'phy:\/dev\/vm1vg1\/wwwserver1-boot,xvda1,w',\n                  'phy:\/dev\/vm1vg1\/wwwserver1-data,xvdb,w',\n              ]\n\n#  Hostname\nname        = 'wwwserver1'\n\n#\n#  Networking\n#\nvif         = &#91;\n        'mac=00:16:3e:9d:a8:fa,bridge=xenbr10,type=vif,vifname=wwws1-dmz',\n        'mac=00:16:3e:fd:4c:77,bridge=xenbr99,type=vif,vifname=wwws1-pvt',\n         ]\n\n\n#\n#  Behaviour\n#\non_poweroff = 'destroy'\non_reboot   = 'restart'\non_crash    = 'restart'\n\n<\/code><\/pre>\n\n\n\n<p>8e. Once the block shifting has completed, we are ready to do the partition\/grub magic. I have two scripts setup with the appropriate commands and they both <strong>assume<\/strong> the target device is attached as <em>xvdb<\/em><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># mbrfixer-step1.sh\n\n#!\/usr\/bin\/env bash\n\n# Install GRUB into the partition\/bootloader space\n\necho \"Make Partition Map\"\n\n## Create partition\/map\nparted \/dev\/xvdb --script \\\n  mklabel msdos \\\n  mkpart primary ext4 1MiB 100% \\\n  set 1 boot on\n\n\n# Expose the partition as a device\nkpartx -av \/dev\/xvdb\n\nPART=\/dev\/mapper\/xvdb1\n\n# Mount and chroot to install GRUB to the MBR\nmount \"$PART\" \/mnt\nmount --bind \/dev  \/mnt\/dev\nmount --bind \/proc \/mnt\/proc\nmount --bind \/sys  \/mnt\/sys\n\n# dump this into the command line so it can easily\n#  be copied\/pasted\/executed in your terminal within the chroot.\necho \"grub-install \/dev\/xvdb;update-grub;exit;\"\n\nchroot \/mnt \/bin\/bash\n<\/code><\/pre>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p> #  .\/mbrfixer-step1.sh<\/p>\n\n\n\n<p>Make Partition Map<br>add map xvdb1 (254:0): 0 20969472 linear 202:16 2048<br>grub-install \/dev\/xvdb;update-grub;exit;<\/p>\n<\/blockquote>\n\n\n\n<p>8f: grab the last line of output from the terminal and paste it in to execute the grub-install from within the chroot onto the mount of the partition created:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>grub-install \/dev\/xvdb;update-grub;exit;<\/code><\/pre>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>\/# grub-install \/dev\/xvdb;update-grub;exit;<br>Installing for i386-pc platform.<br>Installation finished. No error reported.<br>Generating grub configuration file \u2026<br>Found linux image: \/boot\/vmlinuz-4.19.0-25-amd64<br>Found initrd image: \/boot\/initrd.img-4.19.0-25-amd64<br>Found linux image: \/boot\/vmlinuz-4.19.0-18-amd64<br>Found initrd image: \/boot\/initrd.img-4.19.0-18-amd64<br>Found linux image: \/boot\/vmlinuz-4.9.0-17-amd64<br>Found initrd image: \/boot\/initrd.img-4.9.0-17-amd64<br>Warning: os-prober will be executed to detect other bootable partitions.<br>Its output will be used to detect bootable binaries on them and create new boot entries.<br>Found Debian GNU\/Linux 10 (buster) on \/dev\/xvdb1<br>done<br>exit<\/p>\n<\/blockquote>\n\n\n\n<p>8g: run step2 to detach the partition mapper: <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#  .\/mbrfixer-step2.sh<\/code><\/pre>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>del devmap : xvdb1<\/p>\n<\/blockquote>\n\n\n\n<p>9. Back to the Xen dom0 environment and detach from MBRFixer the now block-shifted, partition-map-containing, grub-installed, slightly smaller file-system, boot drive:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># xl block-detach mbrfixerX2 51728<\/code><\/pre>\n\n\n\n<p>Now the boot disk is ready to move over into the ProxMox environment.<\/p>\n\n\n\n<p>10. On the target server, run a netcat listen process that will pipe into the device setup to hold the boot drive contents. (I use LVM):<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># \/dev\/vm3vg1\/vm-156-disk-0 should point to the disk created as part of ProxMox's Create VM wizard.\n\nnc -l -p 19001|dd bs=4M iflag=fullblock oflag=direct of=\/dev\/vm3vg1\/vm-156-disk-0\n\n# Make sure the logical volume is actually marked active.<\/code><\/pre>\n\n\n\n<p>On the source Xen server, start streaming the data from the newly assimilated boot drive to that listening socket:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>pv -B4M \/dev\/vm1vg1\/wwwserver1-boot  |nc -q2 10.0.0.3 19001<\/code><\/pre>\n\n\n\n<p>Other secondary disk containers for the virtual system can be simply copied over without any manipulation, so a set of nc\/pv operations for those will suffice.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p>11. On the ProxMox server, use the Console to launch the VM.<br>It may be necessary to correct network interface device names in the network configuration: <strong>eth0<\/strong> becomes <strong>ens18<\/strong>, that sort of thing. Correct and reboot.<\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Background We have been luddites running plain Xen 4.x hypervisor atop of Debian for way too long. Manually launching\/stopping virtual machines, no live migration, no&#8230;<\/p>\n<div class=\"more-link-wrapper\"><a class=\"more-link\" href=\"https:\/\/www.networkjack.info\/blog\/2026\/08\/29\/migrating-pvgrub-based-xen-domu-virtual-machines-to-proxmox\/\">Continue reading<span class=\"screen-reader-text\">Migrating PVGrub-based Xen domU Virtual Machines to ProxMox<\/span><\/a><\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[14,7,8,70],"tags":[79,80,71],"class_list":["post-348","post","type-post","status-publish","format-standard","hentry","category-colocation","category-linux","category-servers","category-virtualization","tag-proxmox","tag-pvgrub","tag-xen","entry"],"_links":{"self":[{"href":"https:\/\/www.networkjack.info\/blog\/wp-json\/wp\/v2\/posts\/348"}],"collection":[{"href":"https:\/\/www.networkjack.info\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.networkjack.info\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.networkjack.info\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.networkjack.info\/blog\/wp-json\/wp\/v2\/comments?post=348"}],"version-history":[{"count":12,"href":"https:\/\/www.networkjack.info\/blog\/wp-json\/wp\/v2\/posts\/348\/revisions"}],"predecessor-version":[{"id":360,"href":"https:\/\/www.networkjack.info\/blog\/wp-json\/wp\/v2\/posts\/348\/revisions\/360"}],"wp:attachment":[{"href":"https:\/\/www.networkjack.info\/blog\/wp-json\/wp\/v2\/media?parent=348"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.networkjack.info\/blog\/wp-json\/wp\/v2\/categories?post=348"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.networkjack.info\/blog\/wp-json\/wp\/v2\/tags?post=348"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}