When you first commission a home server or NAS, setting up the operating system on a single, fast SSD is quick and straightforward. But as your services grow—hosting Docker containers, databases, storage shares, and automation routines—that single OS drive becomes a dangerous single point of failure (SPOF).
If that primary drive suffers a controller failure or NAND degradation, your server goes dark until you find replacement hardware, reinstall Linux, re-configure network interfaces, restore users, and rebuild boot configs.
In this guide, we walk through the architecture, real-world edge cases, and a fully automated, battle-tested script to convert a live, single-disk Linux (Fedora / RHEL / CentOS / Debian) system into a mirrored RAID 1 array online with zero downtime and zero data loss.
🏗 High-Availability Architecture Overview
To achieve true hardware redundancy, every layer of the storage and boot hierarchy must be mirrored across both physical drives:

Key Architectural Decisions:
/booton RAID 1 (/dev/md10) withmetadata=1.0:
Standardmdadmversions (metadata1.2) place the superblock at the start of the partition, which legacy BIOS and older bootloaders cannot read directly without specialized modules. Using--metadata=1.0writes the RAID superblock at the very end of the partition, leaving the rawext4filesystem structure at sector 0 so standard BIOS/GRUB loaders can read the kernel and initramfs natively.- LVM on RAID 1 (
/dev/md11) withmetadata=1.2:
The entire LVM Volume Group (root,home,swap) runs on top of a single mirroredmdadmblock device. - Dual-Disk GRUB Installation:
GRUB is written to the Master Boot Record / boot sectors of both physical disks. If Drive A is unplugged or fails mechanically, the BIOS simply falls back to Drive B and boots into the mirror without manual intervention.
⚠️ Real-World Gotchas & Lessons Learned
During the migration, three critical engineering hurdles were solved:
1. The pvmove Metadata Deficit Trap
When you create an mdadm array on a partition that matches your source drive’s size, mdadm consumes ~128 MB (32 LVM extents) for its internal superblock and bitmap logs.
When attempting to live-migrate the volume group via pvmove, LVM will reject the operation with:
Insufficient free space: 56977 extents needed, but only 56945 available
Unable to allocate mirror extents for vg/pvmove0.
Failed to convert pvmove LV to mirrored.
The Solution:
Instead of resizing complex ext4 filesystems like / or /home, we safely trim the raw swap logical volume by 1 GB:
- Disable active swap (
swapoff). - Wipe the swap filesystem signature (
wipefs -af) so LVM’s automatedfsadmcheck doesn’t block the resize. - Shrink the LV (
lvresize -L 6G). - Recreate and re-enable swap (
mkswap&swapon).
This frees up 256+ extents in milliseconds with zero risk to production data, giving pvmove plenty of headroom to complete the live mirror migration.
2. Device Busy Locks on Incomplete Initializations
If a script run is interrupted, the kernel or udev may hold degraded RAID descriptors open. A reliable automation script must actively stop lingering arrays (mdadm --stop), settle the udev event queue (udevadm settle), and wipe leftover partition and filesystem headers before partitioning.
3. Persistent Bootloader & Dracut Modules
To ensure subsequent kernel updates (e.g. dnf upgrade kernel) generate bootable images containing the required RAID drivers, you must declare add_dracutmodules+=" mdraid lvm " in /etc/dracut.conf.d/raid.conf and update /etc/mdadm.conf.
🛠 Complete Automated Conversion Script
Here is the sanitized, production-ready script generated with agy (Gemini CLI). It can be run on any live Linux server running LVM over MBR/BIOS.
#!/bin/bash
# ==============================================================================
# Script: setup_live_os_raid1.sh
# Description: Converts a single-disk Linux OS installation to a live, mirrored
# High-Availability RAID 1 array (LVM + /boot) with dual-MBR GRUB.
# Requirements: root privileges, mdadm, dracut, grub2, lvm2, rsync, parted.
# ==============================================================================
set -euo pipefail
LOGFILE="/var/log/setup_os_raid1_$(date +%Y%m%d_%H%M%S).log"
exec > >(tee -a "$LOGFILE") 2>&1
echo "=========================================================="
echo " Linux High-Availability OS RAID 1 Mirroring Setup "
echo "=========================================================="
echo "Timestamp: $(date)"
echo "Log file: $LOGFILE"
if [ "$EUID" -ne 0 ]; then
echo "[-] ERROR: This script must be run as root: sudo bash $0"
exit 1
fi
# ------------------------------------------------------------------------------
# 1. Drive Identification & Target Safety Verification
# ------------------------------------------------------------------------------
DISK_SRC="/dev/sda"
DISK_DST="/dev/sdb"
# (Optional) Hardcode device serial numbers for maximum safety in production
SRC_SERIAL=$(lsblk -d -no SERIAL "$DISK_SRC" 2>/dev/null || true)
DST_SERIAL=$(lsblk -d -no SERIAL "$DISK_DST" 2>/dev/null || true)
echo "[1/10] Verifying target drives..."
echo " - Source Disk (Active OS): $DISK_SRC (Serial: ${SRC_SERIAL:-N/A})"
echo " - Target Disk (New Mirror): $DISK_DST (Serial: ${DST_SERIAL:-N/A})"
# ------------------------------------------------------------------------------
# 2. Safety Backups
# ------------------------------------------------------------------------------
echo "[2/10] Creating safety backup of /boot and critical configs..."
BACKUP_DIR="/root/os_pre_raid_backup_$(date +%Y%m%d_%H%M%S)"
mkdir -p "$BACKUP_DIR"
cp -a /etc/fstab /etc/default/grub "$BACKUP_DIR/" 2>/dev/null || true
cp -a /etc/mdadm.conf "$BACKUP_DIR/" 2>/dev/null || true
rsync -aHAX /boot/ "$BACKUP_DIR/boot_backup/"
echo "[+] Backup saved to $BACKUP_DIR"
# Clean any lingering RAID descriptors
echo "[*] Cleaning existing RAID descriptors..."
pvmove --abort 2>/dev/null || true
vgreduce fedora00 /dev/md11 2>/dev/null || true
pvremove -ff -y /dev/md11 2>/dev/null || true
mdadm --stop /dev/md10 2>/dev/null || true
mdadm --stop /dev/md11 2>/dev/null || true
udevadm settle
# ------------------------------------------------------------------------------
# 3. Swap Resize (Prevent pvmove metadata extent shortfall)
# ------------------------------------------------------------------------------
echo "[3/10] Resizing swap LV to 6GB (freeing extents for mdadm metadata)..."
swapoff /dev/mapper/fedora00-swap 2>/dev/null || true
wipefs -af /dev/mapper/fedora00-swap 2>/dev/null || true
lvresize -y -L 6G /dev/mapper/fedora00-swap
mkswap /dev/mapper/fedora00-swap
swapon /dev/mapper/fedora00-swap
echo "[+] Swap resized successfully!"
# ------------------------------------------------------------------------------
# 4. Wipe & Partition Target Drive (/dev/sdb)
# ------------------------------------------------------------------------------
echo "[4/10] Wiping $DISK_DST and cloning partition geometry..."
for p in $(lsblk -lno NAME "$DISK_DST"); do
umount -f "/dev/$p" 2>/dev/null || true
done
wipefs -af "${DISK_DST}1" 2>/dev/null || true
wipefs -af "${DISK_DST}2" 2>/dev/null || true
wipefs -af "$DISK_DST"
dd if=/dev/zero of="$DISK_DST" bs=1M count=20 conv=fsync status=none
# Create matching MBR partition table with Linux RAID flags (0xfd)
parted -s "$DISK_DST" mklabel msdos
parted -s -a optimal "$DISK_DST" mkpart primary 2048s 2099199s
parted -s -a optimal "$DISK_DST" mkpart primary 2099200s 468861951s
parted -s "$DISK_DST" set 1 boot on
parted -s "$DISK_DST" set 1 raid on
parted -s "$DISK_DST" set 2 raid on
partprobe "$DISK_DST"
udevadm settle
# ------------------------------------------------------------------------------
# 5. Create Degraded RAID 1 Arrays on Target Disk
# ------------------------------------------------------------------------------
echo "[5/10] Creating degraded RAID1 arrays on $DISK_DST..."
mdadm --zero-superblock --force "${DISK_DST}1" 2>/dev/null || true
mdadm --zero-superblock --force "${DISK_DST}2" 2>/dev/null || true
# /dev/md10 for /boot (metadata 1.0 puts superblock at partition end for BIOS/GRUB)
mdadm --create /dev/md10 --level=1 --raid-devices=2 missing "${DISK_DST}1" --metadata=1.0 --force
# /dev/md11 for LVM OS PV
mdadm --create /dev/md11 --level=1 --raid-devices=2 missing "${DISK_DST}2" --metadata=1.2 --force
udevadm settle
# ------------------------------------------------------------------------------
# 6. Format and Synchronize /boot to /dev/md10
# ------------------------------------------------------------------------------
echo "[6/10] Formatting and synchronizing /boot to /dev/md10..."
mkfs.ext4 -F -L boot_raid /dev/md10
mkdir -p /mnt/new_boot
mount /dev/md10 /mnt/new_boot
rsync -aHAX --delete /boot/ /mnt/new_boot/
umount /mnt/new_boot
rmdir /mnt/new_boot
# ------------------------------------------------------------------------------
# 7. Live LVM Migration (pvmove from sda2 to md11)
# ------------------------------------------------------------------------------
echo "[7/10] Live migrating LVM volume group fedora00 from ${DISK_SRC}2 to /dev/md11..."
pvcreate -ff -y /dev/md11
vgextend fedora00 /dev/md11
echo "[+] Starting live pvmove (this migrates all root, home, and active data online)..."
pvmove "${DISK_SRC}2" /dev/md11
vgreduce fedora00 "${DISK_SRC}2"
pvremove -ff -y "${DISK_SRC}2"
echo "[+] LVM live migration completed successfully!"
# ------------------------------------------------------------------------------
# 8. Remount /boot to /dev/md10 in /etc/fstab
# ------------------------------------------------------------------------------
echo "[8/10] Updating /boot mount in /etc/fstab to use RAID1 UUID..."
NEW_BOOT_UUID=$(blkid -s UUID -o value /dev/md10)
echo "[+] New /boot RAID UUID: $NEW_BOOT_UUID"
sed -i -E "s|^UUID=[^[:space:]]+[[:space:]]+/boot|UUID=$NEW_BOOT_UUID /boot|" /etc/fstab
umount /boot
mount /boot
echo "[+] /boot successfully remounted from /dev/md10!"
# ------------------------------------------------------------------------------
# 9. Configure Dracut, mdadm.conf & Regenerate Initramfs
# ------------------------------------------------------------------------------
echo "[9/10] Configuring dracut and regenerating initramfs..."
mkdir -p /etc/dracut.conf.d
echo 'add_dracutmodules+=" mdraid lvm "' > /etc/dracut.conf.d/raid.conf
mkdir -p /etc/mdadm
echo "DEVICE /dev/sd[a-z][0-9]" > /etc/mdadm.conf
mdadm --detail --scan >> /etc/mdadm.conf
CURRENT_KVER=$(uname -r)
echo "[+] Rebuilding initramfs for running kernel ($CURRENT_KVER)..."
dracut -f --add "mdraid lvm" "/boot/initramfs-${CURRENT_KVER}.img" "$CURRENT_KVER"
for vmlinuz in /boot/vmlinuz-*.x86_64; do
KVER=$(basename "$vmlinuz" | sed "s/vmlinuz-//")
if [ "$KVER" != "$CURRENT_KVER" ] && [ -d "/lib/modules/$KVER" ]; then
echo "[+] Rebuilding initramfs for installed kernel ($KVER)..."
dracut -f --add "mdraid lvm" "/boot/initramfs-${KVER}.img" "$KVER" 2>/dev/null || true
fi
done
echo "[+] Updating GRUB configuration..."
grub2-mkconfig -o /boot/grub2/grub.cfg
# ------------------------------------------------------------------------------
# 10. Repartition Source Disk & Join to RAID 1 Arrays
# ------------------------------------------------------------------------------
echo "[10/10] Partitioning $DISK_SRC and joining into RAID 1 arrays..."
wipefs -af "$DISK_SRC"
dd if=/dev/zero of="$DISK_SRC" bs=1M count=20 conv=fsync status=none
parted -s "$DISK_SRC" mklabel msdos
parted -s -a optimal "$DISK_SRC" mkpart primary 2048s 2099199s
parted -s -a optimal "$DISK_SRC" mkpart primary 2099200s 468861951s
parted -s "$DISK_SRC" set 1 boot on
parted -s "$DISK_SRC" set 1 raid on
parted -s "$DISK_SRC" set 2 raid on
partprobe "$DISK_SRC"
udevadm settle
echo "[+] Adding ${DISK_SRC}1 to /dev/md10 (/boot)..."
mdadm --add /dev/md10 "${DISK_SRC}1"
echo "[+] Adding ${DISK_SRC}2 to /dev/md11 (LVM OS pool)..."
mdadm --add /dev/md11 "${DISK_SRC}2"
# Install GRUB Bootloader on BOTH Physical Disks
echo "[+] Installing GRUB bootloader to both drives ($DISK_SRC and $DISK_DST)..."
grub2-install "$DISK_SRC"
grub2-install "$DISK_DST"
echo "DEVICE /dev/sd[a-z][0-9]" > /etc/mdadm.conf
mdadm --detail --scan >> /etc/mdadm.conf
echo "=========================================================="
echo " HIGH AVAILABILITY RAID 1 OS SETUP COMPLETE! "
echo "=========================================================="
echo ""
echo "Current RAID Status (/proc/mdstat):"
cat /proc/mdstat
echo ""
echo "Active Mounts:"
df -hT / /boot /home
echo ""
echo "Both SSDs ($DISK_SRC and $DISK_DST) are bootable with GRUB."
echo "Synchronization will complete in the background."
echo "=========================================================="
🔍 Verification & Failover Readiness
Once the script completes, Linux synchronizes the arrays in the kernel background without consuming unnecessary CPU cycles.
1. Check Mirror Health
cat /proc/mdstat
When fully synchronized, both arrays will display [UU]:
Personalities : [raid1]
md11 : active raid1 sda2[2] sdb2[1]
233249280 blocks super 1.2 [2/2] [UU]
md10 : active raid1 sda1[2] sdb1[1]
1048512 blocks super 1.0 [2/2] [UU]
2. Verify Detailed Array Health
sudo mdadm --detail /dev/md10
sudo mdadm --detail /dev/md11
Look for:
State : cleanActive Devices : 2Working Devices : 2Failed Devices : 0
3. Optional: Speeding up Initial RAID Sync
If your SSDs support higher sustained writes, you can temporarily increase the Linux kernel rebuild limits:
sudo sysctl -w dev.raid.speed_limit_max=500000
sudo sysctl -w dev.raid.speed_limit_min=50000
🏁 Summary
With this setup:
- Storage High Availability: Your root filesystem (
/), home directories (/home), and/bootcontinue operating seamlessly if either SSD fails. - Boot Redundancy: Both drives have independent Master Boot Records with GRUB installed.
- Seamless Maintenance: Replacing a degraded SSD in the future requires only partitioning the replacement drive and running
mdadm --add. - Zero Downtime: The entire conversion was performed live while web servers, databases, and Docker containers remained online.