Migrating a Proxmox VM to Bare Metal: The Disk-Clone Method

A step-by-step walkthrough of cloning a Proxmox VM's disk onto a USB drive and transplanting it onto physical hardware, including the pitfalls that trip people up.
What this tutorial actually does
If you're running a service inside a Proxmox virtual machine and you've decided it deserves real, dedicated hardware, there are two ways to get it there. This tutorial covers the first: cloning the VM's virtual disk directly onto physical hardware, byte for byte, so the bare-metal machine boots up thinking it's still the VM.
The appeal is that everything — your installed packages, your configuration, your data, your running services — comes along for the ride automatically. The trade-off is that you're moving a disk image built for virtual hardware (virtio drivers, a virtual BIOS/UEFI, virtual disk controllers) onto real hardware with real drivers, real firmware quirks, and a real network card. That mismatch is where most of the problems in this process come from, and this guide calls them out as they come up.
If that sounds like more risk than you want, there's a companion tutorial that does a clean install and restores your data on top of it — slower, but far more predictable. It's worth reading the "Which approach should you use?" section at the end before you start.
What you'll need
- SSH or console access to your Proxmox host
- A USB drive with capacity at or above your VM's disk size (if your VM disk is 200GB, your USB needs to be 200GB or larger — smaller means truncated, unusable data)
- The physical machine you're migrating onto
- About 30–90 minutes, depending on disk size and USB transfer speed
Step 1: Shut the VM down before you touch its disk
This step doesn't appear in most versions of this tutorial floating around online, but it matters: don't clone a disk that's still being written to.
qemu-img convert, which you'll use in the next step, reads the raw disk image while it copies it. If the VM is powered on and processes inside it are actively writing to disk — logs rotating, a database flushing, anything — you can end up with a copy that's in the same state as if you'd pulled the power cord mid-write. Modern filesystems usually recover from that fine on next boot, but databases (PostgreSQL, MySQL, etc.) are far more likely to come up corrupted or need a crash-recovery pass.
Shut the VM down cleanly first:
# On the Proxmox host
qm shutdown 100 # replace 100 with your VMID
qm status 100 # wait until it reports "stopped"If you genuinely cannot take downtime, at minimum stop any databases or write-heavy services inside the VM before cloning, and treat the resulting copy the same way you'd treat a copy taken after an unclean shutdown — expect a filesystem check and possible database recovery on first boot of the clone.
Step 2: Confirm the VM's firmware type
This whole method depends on your USB drive booting in UEFI mode, because that's very likely what your VM disk expects. But this only holds if the VM was actually created with UEFI (OVMF) firmware rather than the older SeaBIOS. Check before you assume:
qm config 100 | grep bios- If you see
bios: ovmf, you're using UEFI — the EFI partition on the cloned disk is real and Step 5 (booting the USB) should work as described. - If you see nothing, or
bios: seabios, the VM has no EFI partition at all, and you'll need to boot the USB in legacy BIOS mode instead, or reinstall the VM with OVMF before proceeding. Trying to UEFI-boot a SeaBIOS disk image will just fail to find a bootloader.
Step 3: Locate the VM's disk on the Proxmox host
qm list
qm config 100 | grep -E 'scsi|virtio|ide'
# Example output: scsi0: local-lvm:vm-100-disk-0,size=200GProxmox stores VM disks differently depending on your storage backend, so the actual file path varies:
| Storage type | Typical path |
|---|---|
| LVM-thin | /dev/pve/vm-100-disk-0 |
| ZFS | /dev/zvol/rpool/data/vm-100-disk-0 |
| Directory-backed (qcow2) | /var/lib/vz/images/100/vm-100-disk-0.qcow2 |
Note which one applies to you — you'll need the exact path in the next step.
Step 4: Write the VM disk onto the USB drive
First, identify the USB drive carefully. Getting this wrong means overwriting the wrong disk with no undo:
lsblk -o NAME,SIZE,MODEL,TRANLook for the drive whose size and TRAN column (should say usb) match your USB stick — commonly something like sdb or sdc. Do not proceed until you're certain which device it is.
Then convert the VM's disk onto it, using the path style that matches your storage backend from Step 3:
# LVM-thin
qemu-img convert -O raw /dev/pve/vm-100-disk-0 /dev/sdX
# ZFS
qemu-img convert -O raw /dev/zvol/rpool/data/vm-100-disk-0 /dev/sdX
# qcow2 file
qemu-img convert -O raw /var/lib/vz/images/100/vm-100-disk-0.qcow2 /dev/sdXReplace /dev/sdX with your actual USB device from lsblk. This will take a while for a 200GB disk — there's no progress bar with qemu-img convert, so don't worry if it looks stuck.
When it finishes, verify the partition layout landed correctly:
lsblk /dev/sdX
# Expect roughly: a ~512M EFI partition + the rest as your root partitionStep 5: Boot the target machine from the USB and test it
- Plug the USB into the target machine.
- Enter the BIOS/UEFI boot menu (commonly
F12,F2,Del, orEscdepending on the manufacturer). - Boot the USB in UEFI mode specifically (not "legacy" or "CSM") — assuming your VM used OVMF firmware, as confirmed in Step 2.
- Select the USB as the boot device.
You should see GRUB, then your OS boot normally, then a login prompt — this is your VM, now running on physical hardware directly off the USB stick.
Test it thoroughly before continuing. Log in, check that your services are running, that your data is present, and that networking comes up (it may not — the network interface name on real hardware is often different from the virtual ens18-style name inside the VM, and you'll fix that properly in Step 8). This is your checkpoint: if something's badly wrong here, you haven't touched the internal drive yet, so nothing is lost.
Step 6: Copy the USB image onto the machine's internal drive
Once you're satisfied the USB boots and looks right, copy it onto the internal drive so the machine can run without the USB stick permanently plugged in.
While still booted from the USB, identify the internal drive:
lsblk
# Your internal drive will be something like /dev/nvme0n1 or /dev/sda
# — NOT the device the USB itself is running fromHere's the detail most versions of this tutorial get wrong: don't blindly dd the entire USB device. A plain dd if=/dev/sdX of=/dev/nvme0n1 copies every byte on the USB stick, including empty, unused space beyond your actual ~200GB of data — right up to the internal drive's own size or the USB's full capacity, whichever is smaller. If your USB drive is much larger than your VM disk, this wastes a lot of time, and if the internal drive happens to be smaller than the USB's total capacity — even though your real data would easily have fit — the copy fails or gets truncated.
The safer approach is to limit the copy to the amount of data that actually exists (your original ~200GB), not the full size of the USB device:
# 200GB example: 200 * 1024 = 204800 MiB, at bs=4M that's 51200 blocks
dd if=/dev/sdX of=/dev/nvme0n1 bs=4M count=51200 status=progress conv=fsyncAdjust the count to match your actual VM disk size (check with qm config on the Proxmox host if you're unsure), or simply use a USB drive sized close to your VM disk in the first place so a full-device dd isn't wasteful.
Step 7: Fix the bootloader on the internal drive
Reboot with the USB removed and see if the machine boots straight from the internal drive. If it doesn't, boot from the USB again and repair GRUB via chroot:
mkdir /mnt/target
mount /dev/nvme0n1p2 /mnt/target # root partition
mount /dev/nvme0n1p1 /mnt/target/boot/efi # EFI partition
mount --bind /dev /mnt/target/dev
mount --bind /proc /mnt/target/proc
mount --bind /sys /mnt/target/sys
mount --bind /run /mnt/target/run # helps some update-grub calls succeed cleanly
chroot /mnt/target
grub-install /dev/nvme0n1
update-grub
exit
rebootRemove the USB before this reboot.
Step 8: Clean up after the first real boot
A few things reliably need attention once you're running from the internal drive for the first time:
Regenerate initramfs, so the kernel has the driver modules for your actual hardware (NVMe controllers, real NICs, etc. — things the VM's virtual hardware never needed):
sudo update-initramfs -u -k allFix the network interface name. Virtual NICs inside Proxmox VMs are often named things like ens18; real hardware NICs get names like enp3s0 or eno1. If your netplan config references the old name, networking will silently fail to come up:
ip link # see what your NIC is actually called now
sudo nano /etc/netplan/50-cloud-init.yaml # update the interface name to match
sudo netplan applyExpand the filesystem if the internal drive is bigger than the original VM disk. Check the filesystem type first — the growth command differs by filesystem:
lsblk -f # confirms partition layout and filesystem type
cat /etc/fstab- For ext4 (the common case):
sudo apt update && sudo apt install -y cloud-guest-utils sudo growpart /dev/nvme0n1 2 sudo resize2fs /dev/nvme0n1p2 - For XFS, use
xfs_growfsinstead ofresize2fs(the partition must still be grown withgrowpartfirst):sudo growpart /dev/nvme0n1 2 sudo xfs_growfs / - For btrfs, the filesystem resize step is different again:
sudo growpart /dev/nvme0n1 2 sudo btrfs filesystem resize max /
Using the wrong command for your filesystem will simply fail with an error — it won't corrupt anything, but it's worth knowing in advance rather than guessing.
Quick reference (once you understand the steps above)
# --- ON PROXMOX HOST ---
qm shutdown 100 && qm status 100 # wait for "stopped"
qm config 100 | grep bios # confirm ovmf (UEFI)
qm config 100 | grep -E 'scsi|virtio|ide' # find disk path
lsblk -o NAME,SIZE,MODEL,TRAN # identify USB, carefully
qemu-img convert -O raw /dev/pve/vm-100-disk-0 /dev/sdX
# --- ON TARGET MACHINE, booted from USB ---
lsblk # find internal drive
dd if=/dev/sdX of=/dev/nvme0n1 bs=4M count=<N> status=progress conv=fsync
# --- If it won't boot: chroot and fix GRUB ---
mount /dev/nvme0n1p2 /mnt/target
mount /dev/nvme0n1p1 /mnt/target/boot/efi
mount --bind /dev /mnt/target/dev
mount --bind /proc /mnt/target/proc
mount --bind /sys /mnt/target/sys
mount --bind /run /mnt/target/run
chroot /mnt/target
grub-install /dev/nvme0n1
update-grub
exit
reboot
# --- After booting from internal drive ---
sudo update-initramfs -u -k all
# fix netplan interface name to match `ip link`
sudo growpart /dev/nvme0n1 2 && sudo resize2fs /dev/nvme0n1p2 # or xfs_growfs / btrfs resizeCommon failure points, summarized
| Symptom | Likely cause |
|---|---|
| USB won't boot at all | Booted in legacy/BIOS mode instead of UEFI, or the VM was actually SeaBIOS-based |
| Database won't start / data looks corrupted | VM wasn't shut down cleanly before cloning |
| dd finished but internal drive is "full" almost immediately | Copied the whole USB device instead of just the real data size |
| No network after first bare-metal boot | Interface name in netplan doesn't match the real hardware's NIC name |
resize2fs errors out | Wrong tool for the filesystem — check with lsblk -f first |
| GRUB doesn't find an OS to boot | Bootloader wasn't reinstalled for the new disk — redo the chroot steps |
Which approach should you use?
This method is faster when it works cleanly, and it brings over your exact running state with nothing to reinstall. But it also carries every quirk of the original virtual hardware with it, and troubleshooting a broken boot on unfamiliar hardware can eat more time than it saved.
If you'd rather trade a bit of extra setup time for a much more predictable outcome, see the companion tutorial: "Clean Install + Restore from Proxmox Backup." It installs Ubuntu fresh, natively, on the real hardware, then restores just your data and configuration on top — no virtual drivers, no GRUB repair, no interface-name guessing.

Written by Robin te Hofstee
Technology enthusiast with a deep love for programming. Sharing practical guides on React, Next.js, Linux and self-hosting.
Related Posts
Migrating a Proxmox VM to Bare Metal: The Clean-Install-and-Restore Method
A safer alternative to disk cloning: back up a Proxmox VM, install Ubuntu fresh on bare metal, and restore your data and configuration on top.
Self-Hosting n8n on Proxmox
Deploying n8n on Proxmox using the Community Scripts LXC installer offers data privacy, unlimited execution, and full control compared to n8n Cloud, ideal for a homelab.
Securing a Next.js Application in Production
Securing a Next.js app in production means protecting the client bundle, the server, and the data flow. This post shows how to lock down Content‑Secur


