Skip to content

Migrate VM From One Node to Another Gradually

Updated 8 min read

Updated: 22 October 2026 · Applies to: SolusVM (historical reference; Virtualizor is this project's current VPS platform)

SolusVM is no longer this project's VPS platform. VPS hosting here now runs on Virtualizor, which does not need this manual LVM-snapshot-and-rsync procedure. This article is kept as historical reference for anyone still operating an older SolusVM-based node; it is not a procedure used on current infrastructure.

This gradual migration method suits a VM with a large disk (where a normal SolusVM migration would take many hours) or one that cannot tolerate a long downtime.

I. Prepare the destination node and VM

  1. On the destination node, create a VM identical to the source VM. The disk size must match exactly; other specs should match as closely as possible. Give it a different, temporary IP address.
  2. Boot the new destination VM once to confirm it boots cleanly.
  3. Shut it down again, and keep it shut down for the rest of this process.
  4. Make sure the source node can SSH to the destination node as root without a password, ideally via SSH keys:
    ssh-keygen -t rsa
    Copy the source node's /root/.ssh/id_rsa.pub into the destination node's /root/.ssh/authorized_keys, then test it. For example, with source node source-node and destination node dest-node, from source-node:
    ssh -v -l root -p 22222 dest-node
    You should land on dest-node without a password prompt.

II. Image a snapshot of the source VM to the destination VM

  1. On the destination node, find the destination VM's allocated logical volume (LV). Open its SolusVM-assigned VM ID's config file, for example VMID_DST:
    cd /home/xen/VMID_DST
    cat VMID_DST.cfg
    Find the disk line, for example:
    disk = ['phy:/dev/vg_destnode/VMID_DST_img,xvda1,w', 'phy:/dev/vg_destnode/VMID_DST_swap,xvda2,w']
    Note the image path (here, /dev/vg_destnode/VMID_DST_img) as the "destination LV".
  2. On the source node, do the same for the source VM's config, for example VMID_SRC:
    cd /home/xen/VMID_SRC
    cat VMID_SRC.cfg
    disk = ['phy:/dev/VolGroup00/VMID_SRC_img,xvda1,w', 'phy:/dev/VolGroup00/VMID_SRC_swap,xvda2,w']
    Note the image path (here, /dev/VolGroup00/VMID_SRC_img) as the "source LV".
  3. On the source node, create an LVM snapshot of the source LV:
    lvcreate -s -n /dev/VolGroup00/VMID_SRC_snap -L 10G /dev/VolGroup00/VMID_SRC_img
    lvs
    Confirm the new snapshot appears in the lvs output.
  4. Image the snapshot across to the destination LV over SSH. Run this inside screen or tmux, since it can take hours depending on the LV's size. On the source node:
    screen
    dd if=/dev/VolGroup00/VMID_SRC_snap | ssh -p 22222 dest-node "dd of=/dev/vg_destnode/VMID_DST_img"
    Use the destination node's internal (private) interface IP address in place of its hostname here if possible. While this runs, check top on both nodes to confirm the dd processes are active, or watch bandwidth graphs for both nodes; the source VM keeps running normally throughout. When it finishes, you should see a summary like:
    671088640+0 records in
    671088640+0 records out
    343597383680 bytes (344 GB) copied, 37518.1 seconds, 9.2 MB/s
    Confirm there were no errors; if there were, fix the cause and restart the imaging.
  5. Mount the destination LV to confirm the image copied correctly. On the destination node:
    mount /dev/vg_destnode/VMID_DST_img /home/xen/VMID_DST/mnt
    df -h
    cd /home/xen/VMID_DST/mnt
    Check that the files roughly match the source VM.
  6. Once confirmed, remove the snapshot on the source node:
    lvremove VolGroup00/VMID_SRC_snap
    At this point you have two identical VMs: the source VM still running normally with no downtime, and the destination VM shut down, holding a point-in-time copy of the source's data.

III. Regularly sync the source VM to the destination VM

  1. Confirm the destination node can SSH directly into the source VM itself (not the source node) with key-based auth:
    ssh-keygen -t rsa
    Copy the destination node's id_rsa.pub into the source VM's authorized_keys, then test, for example with destination node dest-node and source VM VMID_SRC:
    ssh -v -l root -p 22222 VMID_SRC
  2. Create a sync script on the destination node, saved as /usr/local/scripts/sync-vm.sh:
    #!/bin/sh
    # Change this according to node type and VM config locations
    node_type=xen # xen/kvm
    vm_home=/home/$node_type
    
    rsync=/usr/bin/rsync
    ssh=/usr/bin/ssh
    ssh_port=22222
    rsync_args="-ave '$ssh -p $ssh_port' --exclude='backup/' --exclude='sys/' --exclude='proc/' --delete --force"
    
    if [[ -z "$1" || -z "$2" ]]; then
        echo "Usage: $0 <Live Source VM IP Address (internal IP recommended)> <Dormant Destination VM ID>"
        echo
        exit
    fi
    
    src_vm_host=$1
    dst_vm_id=$2
    dst_vm_data=$vm_home/$dst_vm_id/mnt/
    
    if [ ! -d "$dst_vm_data/home" ]; then
        echo "ERROR: Destination VM data ( $dst_vm_data ) does NOT exist. Make sure it is typed and mounted properly."
        echo
        exit
    fi
    
    echo "-------------------------------------------------------------------"
    echo "Begin $src_vm_host VM Sync to $dst_vm_data: "`date`
    echo "-------------------------------------------------------------------"
    eval $rsync $rsync_args $src_vm_host:/ $dst_vm_data
    echo "-------------------------------------------------------------------"
    echo "End $src_vm_host VM Sync to $dst_vm_data: "`date`
    echo "-------------------------------------------------------------------"
    echo
  3. Test it from the destination node, inside screen:
    screen
    /usr/local/scripts/sync-vm.sh VMID_SRC VMID_DST
    It should sync the currently running source VM to the dormant destination VM, printing a begin and end line with timestamps.
  4. Once it works, schedule it to run during off-peak hours via /etc/crontab:
    0 19,0,5 * * * root /usr/local/scripts/sync-vm.sh VMID_SRC VMID_DST >> /var/log/sync-vm.log 2>&1
    Adjust the schedule (here, 7pm, midnight and 5am) to the VM's own quiet hours.
  5. Check on it periodically:
    tail -f /var/log/sync-vm.log

IV. Prepare for the switchover

  1. Confirm the destination node can SSH into the source node (not the VM this time) with key-based auth, the same way as in section I, then test it.
  2. Create a pre-switchover sync script on the destination node, saved as /usr/local/scripts/sync-vm-offline.sh. It is similar to the script above, but assumes both VMs are shut down during the sync:
    #!/bin/sh
    # Change this according to node type and VM config locations
    node_type=xen # xen/kvm
    vm_home=/home/$node_type
    
    rsync=/usr/bin/rsync
    ssh=/usr/bin/ssh
    ssh_port=22222
    rsync_args="-ave '$ssh -p $ssh_port' --exclude='backup/' --exclude='sys/' --exclude='proc/' --delete --force"
    
    if [[ -z "$1" || -z "$2" ]]; then
        echo "Usage: $0 <Source node IP Address (internal IP recommended)> <Dormant Source VM ID> <Dormant Destination VM ID>"
        echo
        exit
    fi
    
    src_node_host=$1
    src_vm_id=$2
    dst_vm_id=$3
    src_vm_data=$vm_home/$src_vm_id/mnt/
    dst_vm_data=$vm_home/$dst_vm_id/mnt/
    
    if $ssh -p $ssh_port $src_node_host test -d $src_vm_data/home; then
        echo "OK: Source VM data ( $src_vm_data ) exists and mounted."
        echo
    else
        echo "ERROR: Source VM data ( $src_vm_data ) does NOT exist. Make sure it is typed and mounted properly."
        echo
        exit
    fi
    
    if [ ! -d "$dst_vm_data/home" ]; then
        echo "ERROR: Destination VM data ( $dst_vm_data ) does NOT exist. Make sure it is typed and mounted properly."
        echo
        exit
    fi
  3. Test run it on the destination node to confirm it runs without errors:
    /usr/local/scripts/sync-vm-offline.sh
  4. Run it again with real arguments. Since the source VM's data is not actually mounted at this point, it should correctly error out, which confirms the script itself is ready:
    /usr/local/scripts/sync-vm-offline.sh source-node.example.com VMID_SRC VMID_DST
    Expect: ERROR: Source VM data ( /home/xen/VMID_SRC/mnt/ ) does NOT exist. Getting that error here means you are ready for the switchover.

V. Switch over to the destination VM

  1. Schedule a short downtime window (around an hour) with whoever owns the VM, and remove the regular sync job from /etc/crontab on the destination node.
  2. At the start of the downtime window, shut down the source VM in SolusVM. Confirm both VMs are shut down.
  3. On the source node, mount the source LV you noted in section II:
    mount /dev/VolGroup00/VMID_SRC_img /home/xen/VMID_SRC/mnt
    df -h
    cd /home/xen/VMID_SRC/mnt
    Confirm the files look right.
  4. From the destination node, run the offline sync script for the final, up-to-date copy:
    /usr/local/scripts/sync-vm-offline.sh source-node.example.com VMID_SRC VMID_DST
    This copies the latest changes from the source VM to the destination VM.
  5. Once it finishes, unmount both sides:
    umount /home/xen/VMID_SRC/mnt
    umount /home/xen/VMID_DST/mnt
    Confirm with df -h that neither mount remains.
  6. In SolusVM, note the source VM's main and additional IP addresses, then remove them, replacing the source VM's IP with a single temporary address.
  7. In SolusVM, remove the destination VM's current IP allocations and assign it the exact IP addresses the source VM used to have. Boot the destination VM and confirm it comes up correctly on the new node, with every IP working and every service running.
  8. Leave the source VM dormant for a while rather than removing it immediately, until you are confident the destination VM is stable on the new node.

Related services

KVM VPS hosting

NVMe SSD and full root access, with Free Basic Managed Support.

Managed cPanel VPS

cPanel included; we handle updates, security and monitoring.

Was this answer helpful?

Still stuck? Open a ticket