Replacing the drives under a Linux software-RAID root array sounds scary, but it’s very doable without reinstalling the OS — even when the new drives are smaller than the old ones. This guide walks through a copy-based migration of an mdadm RAID1 root (and swap) onto a fresh pair of drives. It’s written generically so you can substitute your own device names and UUIDs.
Scenario: An mdadm RAID1 mirror holds root and swap. One drive has failed (or you simply want to replace both), and you have a new pair of drives to migrate onto. The new drives may be smaller than the originals, as long as your actual used data fits comfortably on one of them. Crucially, /boot lives on a separate array (or device) that you are not touching — this removes the trickiest part of any root migration.
Two approaches — and why “copy” usually wins
There are broadly two ways to do this:
Shrink in place: shrink the filesystem, shrink the md array, then swap in the new (smaller) drives as mirror members. This works but is risky — a mis-ordered shrink destroys data, and XFS cannot be shrunk at all.
Copy to new arrays: build the new mirror separately, copy root onto it, repoint the boot config, and reboot. This never modifies the old array, so rollback is trivial: if the new disks don’t boot, you revert a couple of config lines and you’re back where you started.
If you can build the new mirror alongside the old one (e.g. you have spare drive slots), the copy approach is both safer and simpler. The rest of this guide uses it.
The golden rule
Until the very last step, the surviving original drive is your only good copy of the data. Do not remove, repartition, or stop the original array until you have booted successfully from the new drives and verified it. Everything before that point should leave the original untouched, so any failure is a simple reboot away from recovery.
Step 0 — Survey what you have
Before changing anything, gather the facts. All read-only:
cat /proc/mdstat
mdadm --detail /dev/mdX # your root array
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT,MODEL
lsblk -f
findmnt / ; swapon --show
cat /proc/cmdline # how the kernel finds root
cat /etc/fstab
cat /etc/mdadm/mdadm.conf # or /etc/mdadm.conf on some distros
The key numbers: the root array’s defined size vs. the new drive’s usable capacity, and how much data is actually used. If the array is defined larger than the new drive but actual usage is small, the copy approach handles the size difference for free — the new array is simply created at the smaller size, and the small dataset fits easily.
Step 1 — Build the new arrays and lay down filesystems
Partition the new drives and create your new RAID1 arrays on them (e.g. one partition for swap, one for root). Once assembled, put filesystems on them and capture the new UUIDs — you’ll need these:
mkfs.ext4 -L newroot /dev/md_newroot
mkswap -L newswap /dev/md_newswap
NEWROOT_UUID=$(blkid -s UUID -o value /dev/md_newroot)
NEWSWAP_UUID=$(blkid -s UUID -o value /dev/md_newswap)
echo "$NEWROOT_UUID $NEWSWAP_UUID"
Step 2 — Copy root across
Mount the new root and copy everything, preserving permissions, ACLs, xattrs, and hardlinks:
mkdir -p /mnt/newroot
mount /dev/md_newroot /mnt/newroot
rsync -aHAXx --info=progress2
--exclude='/proc/*' --exclude='/sys/*' --exclude='/dev/*'
--exclude='/run/*' --exclude='/tmp/*' --exclude='/mnt/*'
--exclude='/media/*' --exclude='/lost+found'
/ /mnt/newroot/
The -x flag is the important one: it keeps rsync on the root filesystem and stops it descending into /boot, other mounts, or virtual filesystems. The explicit excludes cover the pseudo-filesystems. Afterward, recreate the empty mountpoints the excludes skipped:
mkdir -p /mnt/newroot/{proc,sys,dev,run,tmp,mnt,media}
chmod 1777 /mnt/newroot/tmp
Step 3 — Repoint the configuration (inside the new root)
This is what makes the system boot from the new array. Every edit targets files under /mnt/newroot, not the running system.
New UUID vs. cloning the old one. The fresh filesystem gets a new UUID. You can either update the few references to point at it (explicit, recommended) or force the new filesystem to adopt the old UUID (fewer edits). The clone trick is a footgun if both the old and new drives are present at boot — two filesystems with the same UUID make root discovery ambiguous. Prefer the explicit path.
fstab — update the root and swap entries to the new UUIDs. Leave the /boot entry alone:
sed -i "s/OLD_ROOT_FS_UUID/$NEWROOT_UUID/" /mnt/newroot/etc/fstab
sed -i "s/OLD_SWAP_UUID/$NEWSWAP_UUID/" /mnt/newroot/etc/fstab
grep -vE '^s*#' /mnt/newroot/etc/fstab | grep -E 'UUID|swap'
mdadm.conf — add ARRAY lines for the new arrays (by UUID) so the initramfs assembles them.
Step 4 — Regenerate boot artifacts via chroot
If anything was writing during the copy, do a second delta pass with --delete first. Then chroot into the new root (binding the pseudo-filesystems and the shared /boot) and regenerate the initramfs and bootloader config so they pick up the new fstab and mdadm.conf:
for d in proc sys dev dev/pts run; do mount --rbind /$d /mnt/newroot/$d; done
mount --rbind /boot /mnt/newroot/boot
chroot /mnt/newroot /bin/bash
# inside the chroot (Debian/Ubuntu/Proxmox):
update-initramfs -u -k all
update-grub
grep -E 'root=UUID|mduuid' /boot/grub/grub.cfg | head # verify NEW root UUID
exit
Verify that grep shows your new root UUID before rebooting. If it still shows the old one, stop and fix it — do not reboot. (On RHEL-family systems, the equivalents are dracut -f and grub2-mkconfig.)
Then unwind the mounts cleanly. A recursive lazy unmount handles stubborn nested binds:
umount -R -l /mnt/newroot
mount | grep /mnt/newroot # should be empty
Step 5 — Reboot and verify (old drive still installed)
Reboot with the original drive still physically present. This is your safety net: if the new config didn’t take, you simply land back on the old root and nothing is lost.
findmnt / # MUST show the new root array
cat /proc/mdstat # new arrays active and in sync
swapon --show # swap on the new array
systemctl --failed # ideally empty
The go/no-go gate is findmnt /. If it shows the new array, you’ve migrated successfully. If it shows the old one, the boot config didn’t apply — a safe failure to debug, with the original intact.
Step 6 — Retire the old drive (verify by serial, not device number)
Only once you’ve confirmed a clean boot on the new drives: power down and remove the old drive. Identify it by serial number, not by its device node. NVMe and disk enumeration is not stable across reboots — the numbers routinely shuffle when hardware changes — so confirm which physical drive you’re pulling:
lsblk -dno NAME,MODEL,SERIAL /dev/nvme*n1
Match the serial to the drive you intend to remove. Then power off, pull it, and boot again.
Optional — renaming the new arrays to match your old convention
If your new arrays came up with auto-assigned “leftover” names (e.g. md126/md127) and you want them back to md0/md2, there are two layers: the runtime device node (set via mdadm.conf) and the on-disk superblock name (changed with mdadm --assemble --update=name).
A non-root array can be renamed live — turn it off, stop it, reassemble by UUID with the new name:
swapoff /dev/md_newswap
mdadm --stop /dev/md_newswap
mdadm --assemble /dev/md0 --update=name --name=host:0 --uuid=XXXXXXXX:...
swapon /dev/md0
The root array can’t be stopped while mounted, so its superblock rename happens from the initramfs, before root mounts. Add break=mount to the kernel line at the GRUB menu (press e to edit), boot to the (initramfs) prompt, then:
cat /proc/mdstat # note the root array's current node
mdadm --stop /dev/mdXXX
mdadm --assemble /dev/md2 --update=name --name=host:2 --uuid=XXXXXXXX:...
exit # continues the boot
Always assemble by --uuid rather than listing device nodes — the UUID is stamped in the superblock and is immune to enumeration changes. Because root mounts by filesystem UUID (independent of the array name), a botched rename never costs you a boot: worst case you exit, the array keeps its old name, and you retry.
Loose ends worth remembering
A retired drive still carries live mdadm superblocks. If you ever reconnect or reuse it, run mdadm --zero-superblock on its partitions first so it doesn’t try to auto-assemble stale arrays. Left on a shelf, it doubles as a cold snapshot of your pre-migration system.
After returning to full redundancy, confirm array monitoring is actually running (e.g. systemctl status mdmonitor) so you get alerted promptly if a drive drops again.
Why this is low-risk
The whole procedure is built around one idea: never modify your only good copy until a verified-good replacement exists. Boot stays on a separate, untouched array; the new arrays are built and tested before the old drive is pulled; the verification reboot happens with the fallback still installed; and the final rename can’t break booting because mounting is keyed to a filesystem UUID, not an array name. Done in that order, the worst case at any step is “reboot and you’re back where you started.”