{"id":187,"date":"2026-09-07T21:03:41","date_gmt":"2026-09-08T03:03:41","guid":{"rendered":"https:\/\/dionysismedia.ca\/?p=187"},"modified":"2026-09-07T21:03:41","modified_gmt":"2026-09-08T03:03:41","slug":"how-to-migrate-a-vm-between-two-unraid-servers","status":"publish","type":"post","link":"https:\/\/dionysismedia.ca\/?p=187","title":{"rendered":"How to Migrate a VM Between Two Unraid Servers"},"content":{"rendered":"<h1>How to Migrate a VM Between Two Unraid Servers<\/h1>\n<p>Moving a virtual machine from one Unraid box to another isn&#8217;t as scary as it sounds. Under the hood, Unraid VMs are just standard KVM\/QEMU domains, so the process boils down to: copy the disk, copy the config, fix a couple of host-specific details, and boot it up. Here&#8217;s the full walkthrough, based on a real migration I ran recently between two of my own servers (referred to below as <code>Old_Server<\/code> and <code>New_Server<\/code>).<\/p>\n<h2>Before You Start<\/h2>\n<p>A few things to have ready:<\/p>\n<ul>\n<li>Both servers reachable on the same network<\/li>\n<li>SSH access to both (via the Unraid terminal or a direct SSH session)<\/li>\n<li>Enough free space on the destination for the VM&#8217;s disk image<\/li>\n<li>The VM <strong>shut down<\/strong> on the source server \u2014 never copy a running VM&#8217;s disk<\/li>\n<\/ul>\n<h2>Step 1: Find the VM&#8217;s Disk Image<\/h2>\n<p>Unraid usually stores VM disks under <code>\/mnt\/user\/domains\/&lt;vm-name&gt;\/<\/code>, but yours might live somewhere custom (a different share, a pool, or unassigned devices). If you&#8217;re not sure, pull it straight from the VM&#8217;s XML definition:<\/p>\n<pre><code>virsh dumpxml &lt;vm-name&gt; | grep -A2 \"&lt;disk\"<\/code><\/pre>\n<p>This shows the <code>&lt;source file='...'\/&gt;<\/code> path for each attached disk \u2014 note the real path before assuming the default location.<\/p>\n<h2>Step 2: Check the Disk Size<\/h2>\n<pre><code>du -sh \/path\/to\/vdisk1.img<\/code><\/pre>\n<p>This tells you how long the copy will take and whether it&#8217;s worth running in the background.<\/p>\n<h2>Step 3: Copy the Disk to the New Server<\/h2>\n<pre><code>mkdir -p \/path\/on\/New_Server\/domains\/&lt;vm-name&gt;\/\nnohup rsync -avP --progress \/path\/to\/vdisk1.img root@New_Server:\/path\/on\/New_Server\/domains\/&lt;vm-name&gt;\/ &gt; \/root\/vmcopy.log 2&gt;&amp;1 &amp;<\/code><\/pre>\n<p>Running it with <code>nohup<\/code> and backgrounding it (<code>&amp;<\/code>) keeps the transfer alive even if your SSH session drops. Watch progress with:<\/p>\n<pre><code>tail -f \/root\/vmcopy.log<\/code><\/pre>\n<p><strong>A word of caution:<\/strong> if SSH between the two servers isn&#8217;t already using key-based auth, a backgrounded <code>rsync<\/code> can hang silently waiting on a password prompt that never arrives. If nothing shows up in the log after a minute or two, check for stuck processes:<\/p>\n<pre><code>ps aux | grep rsync<\/code><\/pre>\n<p>If you see <code>rsync<\/code>\/<code>ssh<\/code> processes sitting idle, kill them and set up an SSH key first:<\/p>\n<pre><code>ssh-keygen -t ed25519 -N \"\" -f \/root\/.ssh\/id_ed25519\ncat \/root\/.ssh\/id_ed25519.pub | ssh root@New_Server \"mkdir -p \/root\/.ssh &amp;&amp; cat &gt;&gt; \/root\/.ssh\/authorized_keys &amp;&amp; chmod 700 \/root\/.ssh &amp;&amp; chmod 600 \/root\/.ssh\/authorized_keys\"<\/code><\/pre>\n<p>Then retry the copy \u2014 it should run unattended from here on.<\/p>\n<p><em>(Note: Unraid&#8217;s default root filesystem is read-only on part of <code>\/root<\/code>, so you may see a harmless <code>known_hosts<\/code> write warning during SSH connections. It doesn&#8217;t block anything \u2014 the connection still succeeds.)<\/em><\/p>\n<h2>Step 4: Export the VM&#8217;s XML Configuration<\/h2>\n<p>Once the disk copy is running (or finished), grab the VM&#8217;s full definition from the source server:<\/p>\n<pre><code>virsh dumpxml &lt;vm-name&gt; &gt; \/root\/vm-export.xml<\/code><\/pre>\n<p>Take a look through it for a few things that will need adjusting for the new host:<\/p>\n<ul>\n<li><strong>Disk path<\/strong> \u2014 update if the destination path differs from the source<\/li>\n<li><strong>Attached ISOs (cdrom entries)<\/strong> \u2014 these almost certainly won&#8217;t exist on the new server; safe to remove if the OS is already installed<\/li>\n<li><strong>CPU pinning (<code>&lt;cputune&gt;<\/code>)<\/strong> \u2014 if the two servers have different CPU models or core counts, pinned core IDs from the old server won&#8217;t map correctly to the new one&#8217;s topology. Easiest fix is to delete the entire <code>&lt;cputune&gt;<\/code> block and let the new host&#8217;s scheduler handle placement<\/li>\n<li><strong>Network bridge name<\/strong> \u2014 confirm the new server uses the same bridge name (commonly <code>br0<\/code>); check with <code>ip a | grep br0<\/code> on the destination<\/li>\n<\/ul>\n<h2>Step 5: Handle the UEFI NVRAM File (If Applicable)<\/h2>\n<p>If your VM uses UEFI\/OVMF firmware, it has a small NVRAM file tied to its UUID, stored at:<\/p>\n<pre><code>\/etc\/libvirt\/qemu\/nvram\/&lt;uuid&gt;_VARS-pure-efi.fd<\/code><\/pre>\n<p>Copy this over too, to preserve the exact UEFI state:<\/p>\n<pre><code>scp \/etc\/libvirt\/qemu\/nvram\/&lt;uuid&gt;_VARS-pure-efi.fd root@New_Server:\/root\/<\/code><\/pre>\n<p>On the new server:<\/p>\n<pre><code>mkdir -p \/etc\/libvirt\/qemu\/nvram\/\nmv \/root\/&lt;uuid&gt;_VARS-pure-efi.fd \/etc\/libvirt\/qemu\/nvram\/<\/code><\/pre>\n<p>If you skip this, libvirt will just generate a fresh NVRAM file \u2014 the VM will still boot, but any custom UEFI boot order or settings will reset to firmware defaults.<\/p>\n<h2>Step 6: Verify the Firmware File Exists<\/h2>\n<p>Before starting the VM, confirm the OVMF loader file referenced in the XML actually exists on the new server:<\/p>\n<pre><code>ls -la \/usr\/share\/qemu\/ovmf-x64\/OVMF_CODE-pure-efi.fd<\/code><\/pre>\n<p>Both servers running the same Unraid version should already have this in place.<\/p>\n<h2>Step 7: Define and Start the VM<\/h2>\n<p>Copy your edited XML to the new server, then define the VM:<\/p>\n<pre><code>virsh define \/root\/vm-export.xml\nvirsh list --all<\/code><\/pre>\n<p>You should see the VM listed as <code>shut off<\/code>. Start it:<\/p>\n<pre><code>virsh start &lt;vm-name&gt;\nvirsh list --all<\/code><\/pre>\n<p>It should now show as <code>running<\/code>.<\/p>\n<h2>Step 8: Verify Everything Actually Works<\/h2>\n<p>Give the guest OS a minute to boot, then confirm network connectivity:<\/p>\n<pre><code>ping -c 4 &lt;vm-ip&gt;<\/code><\/pre>\n<p>If that responds, SSH in and check that whatever services the VM runs actually came back up cleanly. If a service normally runs under systemd, check it there; if not, a simple process check works just as well:<\/p>\n<pre><code>ps aux | grep &lt;process-name&gt;<\/code><\/pre>\n<h2>Step 9: Clean Up (Whenever You&#8217;re Ready)<\/h2>\n<p>Once you&#8217;re confident the VM is stable on its new home \u2014 I&#8217;d recommend giving it at least a day of normal use first \u2014 you can remove the old copy from the source server:<\/p>\n<pre><code>virsh undefine &lt;vm-name&gt; --nvram\nrm -rf \/path\/to\/old\/domains\/&lt;vm-name&gt;\/<\/code><\/pre>\n<p>There&#8217;s no rush on this step. Keeping the old copy around costs disk space, but it&#8217;s a free safety net until you&#8217;re sure the migration is solid.<\/p>\n<hr \/>\n<h2>Summary<\/h2>\n<p>Migrating a VM between Unraid servers is really just a disk copy plus an XML config with a few host-specific edits. The main gotchas are CPU pinning that doesn&#8217;t map across different hardware, SSH auth that isn&#8217;t set up yet, and remembering the NVRAM file if your VM uses UEFI. Once you&#8217;ve done it once, it&#8217;s a pretty quick process the next time around.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Migrating a VM between Unraid servers: copy the disk, export and edit the XML config, handle UEFI NVRAM, then define and start the VM on the new host.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[13],"class_list":["post-187","post","type-post","status-publish","format-standard","hentry","category-general","tag-unraid"],"_links":{"self":[{"href":"https:\/\/dionysismedia.ca\/index.php?rest_route=\/wp\/v2\/posts\/187","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/dionysismedia.ca\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dionysismedia.ca\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dionysismedia.ca\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/dionysismedia.ca\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=187"}],"version-history":[{"count":0,"href":"https:\/\/dionysismedia.ca\/index.php?rest_route=\/wp\/v2\/posts\/187\/revisions"}],"wp:attachment":[{"href":"https:\/\/dionysismedia.ca\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=187"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dionysismedia.ca\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=187"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dionysismedia.ca\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=187"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}