AlmaLinux

Configure mdadm Software RAID on Rocky Linux 10 / AlmaLinux 10

An mdadm software RAID array on Rocky Linux survives a dead disk only if three files around it are right as well. You need /etc/mdadm.conf, a safe mount entry and a monitor that tells you a disk died. Skip mdadm.conf and the array can come back as /dev/md127 in read-only mode, while mdmonitor refuses to start, so no failure email ever leaves the box. We hit both on a test server.

Original content from computingforgeeks.com - post 113486

You’ll build software RAID 1, 5 or 10, mount it with XFS, pin its name, wire up email alerts, add a hot spare and then fail a disk on purpose to time the rebuild. Every command was run on Rocky Linux 10.2 (kernel 6.12, mdadm 4.4) with SELinux enforcing in October 2026. AlmaLinux 10 ships the identical mdadm-4.4-3.el10_2.2 build, so the steps carry over to AlmaLinux and RHEL 10.

Prerequisites

You need a Rocky Linux 10, AlmaLinux 10 or RHEL 10 server with sudo access and a set of empty disks that the operating system does not live on. Everything on those disks is destroyed in the partition step, so check twice which ones they are:

  • 2 disks for RAID 1, 3 for RAID 5, 4 for RAID 10
  • one extra disk if you want a hot spare
  • disks of the same size, ideally the same model
  • the mdadm, parted, postfix and s-nail packages from BaseOS and AppStream

Before you buy anything, work out two numbers from the disk size. Those are usable capacity and rebuild time. mdadm uses the smallest member’s size for every member, so a 4 TB disk mixed with 8 TB disks wastes the difference. Rebuild time grows with capacity, because md copies the whole member, not just your data. If you are still choosing drives, our NAS hard drive comparison covers CMR versus SMR and price per TB, and SMR drives are the ones to avoid under RAID because their rewrite speed collapses during a rebuild. A server that has run out of bays can take more disks through a JBOD enclosure, as long as it presents each disk individually rather than doing its own RAID.

Our lab VM had 2 vCPU, 2 GB RAM and four 10 GB virtual disks. That is a floor for following along, not a production recommendation: md itself needs little CPU or RAM, but the disks and the controller set the rebuild speed, so size by disk count and capacity instead.

Pick the RAID level

RAID 1 is the right default for two disks, RAID 10 for four or more when you care about write speed, and RAID 5 when capacity matters more than rebuild risk. The table shows what each level gave us on 10 GB members, including how long the initial sync took with md’s default speed cap:

LevelDisksUsable space (10 GB members)SurvivesInitial sync in our lab
RAID 1 (mirror)29.99 GiB1 disk58 s
RAID 5 (striping with parity)319.98 GiB1 disk84 s
RAID 10 (striped mirrors, near=2)419.98 GiB1 disk per mirror pair109 s

RAID 10 took longest because md resyncs the full 20 GiB array, while RAID 1 only mirrors one 10 GiB member. RAID 1 and RAID 10 averaged about 185 and 197 MB/s, close to the kernel’s default ceiling of 200000 KB/s that we read from /proc/sys/dev/raid/speed_limit_max. RAID 5 averaged only about 128 MB/s, even though its first progress line showed 300 MB/s. Treat that ceiling as the best case you plan around: even at a full 200 MB/s, a single 10 TB member needs about 14 hours, and RAID 5 runs that long with no redundancy left. That is why we’d pick RAID 10 or RAID 6 over RAID 5 once disks pass a few TB. If you’re still weighing md against LVM mirroring or ZFS, the RAID vs LVM vs ZFS comparison covers the trade-offs.

Install mdadm and identify the disks

Our Rocky Linux image already had mdadm installed, but installing it explicitly costs nothing and makes sure parted is there for the next step.

sudo dnf install -y mdadm parted

Confirm which version you got. The output is a single line.

mdadm --version

The BaseOS build reports itself like this.

mdadm - v4.4 - 2024-11-07 - 3

Now find the disks. Do not trust the sdX letters alone, because the kernel hands them out in probe order and that order changes between boots. List each disk with its model and serial so you can tie a name to a physical drive:

lsblk -d -o NAME,SIZE,TYPE,MODEL,SERIAL,ROTA

Our four data disks are the 10G entries with no partitions, and sda holds the operating system:

NAME SIZE TYPE MODEL         SERIAL      ROTA
sda   10G disk QEMU HARDDISK drive-scsi0    1
sdb   10G disk QEMU HARDDISK drive-scsi3    1
sdc   10G disk QEMU HARDDISK drive-scsi4    1
sdd   10G disk QEMU HARDDISK drive-scsi2    1
sde   10G disk QEMU HARDDISK drive-scsi1    1
sr0    4M rom  QEMU DVD-ROM  QM00003        1

Notice that sdb is the disk in slot 3 and sde is in slot 1, so the letters already do not follow the hardware. After later reboots on the same VM, the operating system disk came up as sdb and one data disk as sda. md itself does not care, because it finds members by the UUID in their superblock. You should care whenever you type a device name into a destructive command, so run lsblk again right before each one. The stable names live in /dev/disk/by-id/, and on real hardware they carry the drive model and serial number printed on its label.

Partition the disks for RAID

mdadm can use whole disks, but a single partition per disk is safer. Other tools then see a partition table instead of a disk that looks empty, and the raid flag tells anyone reading it what the partition is for. Starting at 1 MiB keeps the partition aligned to the physical sectors of modern disks. This loop writes a GPT label, one full-size partition and the raid flag on each data disk. Edit the list to match your own lsblk output before you run it:

for disk in /dev/sdb /dev/sdc /dev/sdd /dev/sde; do
  sudo parted --script "$disk" mklabel gpt mkpart primary 1MiB 100% set 1 raid on
done

Check one of them before you go further, because a typo in the loop shows up here.

sudo parted --script /dev/sdb print

Partition 1 should span the disk and carry the raid flag:

Model: QEMU QEMU HARDDISK (scsi)
Disk /dev/sdb: 10.7GB
Sector size (logical/physical): 512B/512B
Partition Table: gpt
Disk Flags:

Number  Start   End     Size    File system  Name     Flags
 1      1049kB  10.7GB  10.7GB               primary  raid

If the flag column is empty or the size is wrong, rerun the loop for that disk before you create the array, because mdadm takes whatever partition you hand it.

Create the mdadm RAID array

Pick one of the three commands below. Each one creates /dev/md0 with version 1.2 metadata and an internal write-intent bitmap. Pass --bitmap=internal yourself, because current mdadm otherwise stops to ask about it first, and the bitmap is what makes a briefly disconnected disk resync in seconds instead of minutes (measured further down). The array is usable as soon as the command returns, while the initial sync runs in the background.

Create RAID 1 with two disks

A mirror keeps a full copy on each disk, so you get the capacity of one disk and can lose either.

sudo mdadm --create /dev/md0 --level=1 --raid-devices=2 --bitmap=internal /dev/sdb1 /dev/sdc1

RAID 1 is the only level that asks a question here. The warning is about booting from the array, which this data array does not need, so answer y. We piped the answer in for this capture, so the y doesn’t appear after the prompt:

mdadm: Note: this array has metadata at the start and
    may not be suitable as a boot device.  If you plan to
    store '/boot' on this device please ensure that
    your boot-loader understands md/v1.x metadata, or use
    --metadata=0.90
Continue creating array [y/N]? mdadm: Defaulting to version 1.2 metadata
mdadm: array /dev/md0 started.

If you plan to boot from a mirror later, that’s a separate design with its own bootloader steps, so keep this array for data.

Create RAID 5 with three disks

RAID 5 stripes data with rotating parity, so three disks give you two disks of space and survive one failure. This is the array the rest of this guide continues with, keeping sde1 free for the hot spare:

sudo mdadm --create /dev/md0 --level=5 --raid-devices=3 --bitmap=internal /dev/sdb1 /dev/sdc1 /dev/sdd1

RAID 5 creates without a prompt and returns the same two lines about version 1.2 metadata and the array starting.

Create RAID 10 with four disks

RAID 10 stripes across mirrored pairs. You get half the raw space, it writes no parity, so random writes don’t pay the RAID 5 penalty, and it survives one failure in each pair:

sudo mdadm --create /dev/md0 --level=10 --raid-devices=4 --bitmap=internal /dev/sdb1 /dev/sdc1 /dev/sdd1 /dev/sde1

For RAID 10, the member list in mdadm --detail shows how the default near=2 layout paired the disks. The mirror pairs are disks 0 and 1, and disks 2 and 3. set-A (disks 0 and 2) and set-B (disks 1 and 3) each hold one complete copy of the data. The array survives losing every disk in one set, because that is one disk per mirror pair. Losing one disk from each set can take out both halves of a pair, and then the array is gone:

            Layout : near=2
        Chunk Size : 512K

Consistency Policy : bitmap

              Name : raid01:0  (local to host raid01)
              UUID : 4143c99c:1922afb3:f990c271:72deb20f
            Events : 21

    Number   Major   Minor   RaidDevice State
       0       8       17        0      active sync set-A   /dev/sdb1
       1       8       33        1      active sync set-B   /dev/sdc1
       2       8       49        2      active sync set-A   /dev/sdd1
       3       8       65        3      active sync set-B   /dev/sde1

The Name : raid01:0 (local to host raid01) line matters later. mdadm stamped the server’s hostname into the superblock, and that stamp decides what the array is called at the next boot.

Watch the initial sync

The kernel’s view of every md array lives in /proc/mdstat, and it is the first file to read whenever you’re unsure what an array is doing:

cat /proc/mdstat

Right after we created the RAID 5 array, it looked like this. You’ll see a progress bar and an estimated finish time.

Personalities : [raid1] [raid4] [raid5] [raid6]
md0 : active raid5 sdd1[3] sdc1[1] sdb1[0]
      20948992 blocks super 1.2 level 5, 512k chunk, algorithm 2 [3/2] [UU_]
      [=>...................]  recovery =  5.7% (600960/10474496) finish=0.5min speed=300480K/sec
      bitmap: 1/1 pages [4KB], 65536KB chunk

unused devices: <none>

That [3/2] [UU_] and the word recovery look alarming on a brand new array, but they are normal for RAID 5. md builds it degraded and computes the third member from the other two. That is faster than a full resync. RAID 1 and RAID 10 show resync with [UU] or [UUUU] instead. When the progress line disappears and every slot reads U, the array has full redundancy. You can format and use it during the sync, but a disk that fails before it finishes takes data with it, so don’t load anything you can’t replace until then.

Create the XFS filesystem and mount it

XFS is the Rocky Linux default. On an md device, mkfs.xfs reads the chunk size and data disk count and aligns itself to them. ext4 works too if you prefer it, and our ext4 vs XFS comparison covers where each one wins. Format the array:

sudo mkfs.xfs /dev/md0

Look for the sunit and swidth line. With a 512K chunk, sunit=128 blocks of 4 KiB is one chunk, and swidth=256 is two chunks, the two data disks of a three-disk RAID 5:

meta-data=/dev/md0               isize=512    agcount=16, agsize=327296 blks
         =                       sectsz=512   attr=2, projid32bit=1
         =                       crc=1        finobt=1, sparse=1, rmapbt=1
         =                       reflink=1    bigtime=1 inobtcount=1 nrext64=1
         =                       exchange=0   metadir=0
data     =                       bsize=4096   blocks=5236736, imaxpct=25
         =                       sunit=128    swidth=256 blks
naming   =version 2              bsize=4096   ascii-ci=0, ftype=1, parent=0
log      =internal log           bsize=4096   blocks=16384, version=2
         =                       sectsz=512   sunit=8 blks, lazy-count=1
realtime =none                   extsz=4096   blocks=0, rtextents=0
         =                       rgcount=0    rgsize=0 extents
         =                       zoned=0      start=0 reserved=0

Create the mount point and mount it by hand once. You’ll add the permanent fstab entry after mdadm.conf is in place.

sudo mkdir -p /srv/raid
sudo mount /dev/md0 /srv/raid

With SELinux enforcing, check the label on the new mount point before any service writes to it. A fresh XFS root directory has no label at all:

ls -ldZ /srv/raid

Ours came back unlabeled_t, which confined services such as Samba, nginx or a database are denied access to:

drwxr-xr-x. 2 root root system_u:object_r:unlabeled_t:s0 6 Oct 16 13:23 /srv/raid

Apply the policy’s default label for that path. You don’t need a custom rule for /srv, because the targeted policy already defines one.

sudo restorecon -Rv /srv/raid

restorecon reports each change it makes. No output would mean the label was already correct.

Relabeled /srv/raid from system_u:object_r:unlabeled_t:s0 to system_u:object_r:var_t:s0

var_t is the generic label for /srv. When a specific service will own the data, give the directory that service’s type with semanage fcontext and run restorecon again rather than turning SELinux off. If you want to carve the array into several resizable volumes instead of one filesystem, put LVM on top of the RAID device before formatting.

Save mdadm.conf so the array keeps its name

Without /etc/mdadm.conf, udev still assembles the array at boot, but the name it gets depends on the hostname stamped in the superblock. On the same host, the array came back as md0. To see what happens when the disks move to another server, or when you rename the host after building the array, we changed the hostname to nas02 and rebooted:

Personalities : [raid4] [raid5] [raid6]
md127 : active (auto-read-only) raid5 sdb1[0] sdc1[3] sde1[1]
      20948992 blocks super 1.2 level 5, 512k chunk, algorithm 2 [3/3] [UUU]
      bitmap: 0/1 pages [0KB], 65536KB chunk

unused devices: <none>

The array became md127, the device link turned into /dev/md/raid01:0, and it sat in auto-read-only until the first write. Anything that referenced /dev/md0 broke. On that same boot the members also swapped letters (they were sdb1, sdc1 and sdd1 when we built it), which proves again why names in commands need checking. The fix is an ARRAY line in mdadm.conf that names the device and identifies the array by UUID. On the host that built the array, generate it from the running array:

sudo mdadm --detail --scan | sudo tee /etc/mdadm.conf

The file now holds one ARRAY line. Your UUID will differ, and the scan may print /dev/md/0 instead of /dev/md0, which works the same way. If the array is already showing up as md127, the scan prints ARRAY /dev/md/raid01:0 instead, and you must edit that name to /dev/md0 before saving, which is what we did on nas02.

ARRAY /dev/md0 metadata=1.2 UUID=887e0290:5db58f14:ff7e0c94:7875f51d

With that line in place, we rebooted again while the host was still called nas02, and the array assembled as md0. We also ran sudo dracut -f, and sudo lsinitrd | grep mdadm.conf came back empty: our initramfs carries the mdraid module but not mdadm.conf. A data array is assembled after the real root is mounted, from the mdadm.conf on disk. Run the same grep on your server, though, because dracut can copy the file in. If it shows up there, run sudo dracut -f after every mdadm.conf change so the two copies don’t disagree at boot. It matters most when / or /boot itself sits on md. The file has a second job, too. mdmonitor does not start without it, which the alerting section below runs into.

Mount the array at boot with fstab

Mount by filesystem UUID rather than /dev/md0, so a renamed array still mounts. Add nofail as well, because without it an array that fails to assemble drops the whole server into emergency mode, and you cannot reach it over SSH to fix it. This one line reads the UUID and appends the entry:

echo "UUID=$(sudo blkid -s UUID -o value /dev/md0) /srv/raid xfs defaults,nofail 0 0" | sudo tee -a /etc/fstab

The appended line looks like this, with your own UUID.

UUID=50fb9497-3220-4ecf-91a3-b6bc1fd399c9 /srv/raid xfs defaults,nofail 0 0

Reload systemd so it picks up the new mount unit, then test the entry without a reboot. Unmount first, because mount -a skips a filesystem that is already mounted, and with nofail a wrong UUID would otherwise pass silently:

sudo systemctl daemon-reload
sudo umount /srv/raid
sudo mount -a
findmnt /srv/raid

A working entry shows /dev/md0 as the source. The mount options also show XFS picked up the stripe geometry (sunit and swidth are in 512-byte sectors here):

TARGET    SOURCE   FSTYPE OPTIONS
/srv/raid /dev/md0 xfs    rw,relatime,seclabel,attr2,inode64,logbufs=8,logbsize=32k,sunit=1024,swidth=2048,noquota

If mount -a prints an error or findmnt shows nothing, fix the line now. A typo in fstab is far easier to correct from a working shell than from the emergency console. findmnt can also lint the whole file:

sudo findmnt --verify

A clean fstab prints Success, no errors or warnings detected. One caveat comes with nofail: if the array doesn’t assemble, the server boots with /srv/raid as an empty directory on the root filesystem, and a service that writes there fills your root disk instead. Give any service that uses the array a RequiresMountsFor=/srv/raid drop-in. For an NFS export of the array, open a drop-in for the NFS server:

sudo systemctl edit nfs-server.service

Add these two lines in the editor that opens, then save and quit:

[Unit]
RequiresMountsFor=/srv/raid

systemd turns that into Requires= and After= on srv-raid.mount. When the array fails to assemble, the mount unit fails and the service stays down, instead of writing into the root filesystem.

Enable mdmonitor email alerts

A degraded RAID array keeps running without a sound, which is the whole problem: the first failure costs nothing, the second costs everything, and you only hear about the second one. Rocky Linux ships mdmonitor.service enabled, but that doesn’t help on its own. To show why, we moved mdadm.conf aside, rebooted and read the unit’s journal:

sudo journalctl -b -u mdmonitor

systemd never started the monitor, and systemctl is-active mdmonitor printed inactive:

Oct 16 13:55:21 raid01 systemd[1]: mdmonitor.service - Software RAID monitoring and management was skipped because of an unmet condition check (ConditionPathExists=/etc/mdadm.conf).

The unit has ConditionPathExists=/etc/mdadm.conf, so with the file from the previous section in place it starts, but it still needs somewhere to send mail. The simplest setup that always works is local delivery to root through Postfix, which listens only on localhost by default and needs no relay. Install Postfix and the mail client:

sudo dnf install -y postfix s-nail
sudo systemctl enable --now postfix

Tell mdadm where alerts go by adding a MAILADDR line to mdadm.conf.

echo "MAILADDR root" | sudo tee -a /etc/mdadm.conf

Restart mdmonitor, don’t just enable it. Ours was already running from the reboot, and a running monitor keeps the config it read at start, so enable --now would have left it with no address:

sudo systemctl enable mdmonitor
sudo systemctl restart mdmonitor

Send a test alert for every array in mdadm.conf. You should never trust an alert path you haven’t seen work.

sudo mdadm --monitor --scan --test --oneshot

mdadm confirms it raised the event. That line alone doesn’t prove the mail left, so check the mailbox next.

mdadm: TestMessage event detected on md device /dev/md0

Read root’s mailbox to confirm you got the message delivered, not just generated.

sudo mail -H -f /var/spool/mail/root

One message from “mdadm monitoring” means the whole chain works. You can open it with sudo mail -f /var/spool/mail/root, then type p and press Enter to print it.

▸N  1 mdadm monitoring      2026-10-16 13:30   23/880   "TestMessage event on "

The body includes a copy of /proc/mdstat at the moment of the event, which helps when you read it on a phone. Local mail only works if somebody reads it. On a real server, forward root to an address you watch with an alias in /etc/aliases, or point Postfix at a relay such as the one in our Rocky Linux mail server guide. Two details from the man page shape what you’ll receive:

  • mail goes out only for Fail, FailSpare, DegradedArray, SparesMissing and TestMessage
  • every other event (SpareActive, RebuildStarted, RebuildFinished) reaches the journal only, because the unit runs with --syslog
  • the polling delay defaults to 60 seconds, tunable with MONITORDELAY in mdadm.conf

The man page says the delay applies when no event happened, and mdadm also waits on /proc/mdstat for changes, so treat our timing as one observation, not a rule. In our failure test below, run right after a monitor restart, the kernel logged the failed disk at 13:30:54 and the Fail email went out at 13:31:52, 58 seconds later.

Add a hot spare

A hot spare is an idle member that md pulls in the moment another member fails, so the rebuild starts before you’ve even read the alert. On a box nobody visits every day, that’s the best use of one extra disk. Add it by its /dev/disk/by-id path rather than its sdX letter, because by this point in our lab the reboots had already shuffled the names and the free disk was sdc. List the partition links and pick the one that points at a partition not listed in /proc/mdstat:

ls -l /dev/disk/by-id/ | grep part1

On our VM, sdb1 was the 2 MB boot partition on the operating system disk and sda1, sdd1 and sde1 were array members, which left drive-scsi1 as the free disk. On real hardware these names carry the model and serial number:

lrwxrwxrwx. 1 root root 10 Oct 16 13:27 scsi-0QEMU_QEMU_HARDDISK_drive-scsi0-part1 -> ../../sdb1
lrwxrwxrwx. 1 root root 10 Oct 16 13:27 scsi-0QEMU_QEMU_HARDDISK_drive-scsi1-part1 -> ../../sdc1
lrwxrwxrwx. 1 root root 10 Oct 16 13:27 scsi-0QEMU_QEMU_HARDDISK_drive-scsi2-part1 -> ../../sdd1
lrwxrwxrwx. 1 root root 10 Oct 16 13:27 scsi-0QEMU_QEMU_HARDDISK_drive-scsi3-part1 -> ../../sde1
lrwxrwxrwx. 1 root root 10 Oct 16 13:27 scsi-0QEMU_QEMU_HARDDISK_drive-scsi4-part1 -> ../../sda1

Confirm where the chosen link points before you use it:

readlink -f /dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi1-part1

It resolved to the unused partition, /dev/sdc1, so add it:

sudo mdadm --manage /dev/md0 --add /dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi1-part1

On a healthy, complete array an added disk becomes a spare, marked (S) in mdstat and spare in the detail output:

    Number   Major   Minor   RaidDevice State
       0       8       65        0      active sync   /dev/sde1
       1       8        1        1      active sync   /dev/sda1
       3       8       49        2      active sync   /dev/sdd1

       4       8       33        -      spare   /dev/sdc1

Regenerate mdadm.conf now, because the scan output gains spares=1. With that recorded, mdmonitor knows the array is meant to have a spare and mails a SparesMissing alert when it doesn’t, which is how you find out the spare was used up. Rewrite the file with both lines and restart the monitor:

{ sudo mdadm --detail --scan; echo "MAILADDR root"; } | sudo tee /etc/mdadm.conf
sudo systemctl restart mdmonitor

The file should now read as follows, and you can diff it against yours.

ARRAY /dev/md0 metadata=1.2 spares=1 UUID=887e0290:5db58f14:ff7e0c94:7875f51d
MAILADDR root

Repeat this regeneration whenever you add or remove a spare, so the count mdmonitor checks matches what you actually run.

Replace a failed disk in mdadm

Practise this on a test array before you need it for real, because a disk swap under pressure is where people remove the wrong disk. Before failing anything, we wrote a 2 GiB file of random data to the array and saved its checksum, so we could prove afterwards that nothing was lost. Then we marked one active member as failed, the same state md enters on its own when a disk throws I/O errors:

sudo mdadm --manage /dev/md0 --fail /dev/sda1

Within five seconds the spare had taken the failed slot and was rebuilding. In mdstat, the failed member carries (F), the array shows [3/2] [U_U], and the recovery line gives an estimate:

mdadm RAID 5 hot spare rebuilding after a disk failure on Rocky Linux 10

The 10 GiB rebuild ran from 13:30:54 to 13:31:48, so 54 seconds. The speed fell from 250 MB/s to about 130 MB/s partway through. mdmonitor then logged the Fail and SpareActive events and mailed the Fail alert. The checksum still matched once the rebuild finished, which you can see in the last line.

mdmonitor Fail and SpareActive events in the journal for an mdadm array on Rocky Linux 10

The failed disk stays listed as faulty until you remove it, and that’s the first step of the actual replacement. Before pulling hardware, match the faulty name to a serial with ls -l /dev/disk/by-id/, so you pull the right drive. Remove the failed member from the array:

sudo mdadm --manage /dev/md0 --remove /dev/sda1

Swap the disk, then give the new one the same partition layout as the others. Check its name with lsblk first, since a hot-plugged disk can take any free letter:

sudo parted --script /dev/sda mklabel gpt mkpart primary 1MiB 100% set 1 raid on

If you’re reusing a disk that was once in an array, including the one you just removed, wipe its old md superblock before adding it. With the old superblock still there, mdadm may treat the disk as a returning member and re-add it from the bitmap, or refuse it and suggest --zero-superblock. Neither is what you want for a replacement, so start it clean:

sudo mdadm --zero-superblock /dev/sda1

Add the new partition to the array. You can watch the result in /proc/mdstat straight away.

sudo mdadm --manage /dev/md0 --add /dev/sda1

Because the spare already rebuilt the array, the new disk becomes the next spare, and the array is back to the layout mdadm.conf describes. Without a spare, the same --add starts the rebuild straight onto the new disk. Our RAID 5 lab stopped at the fail step, so the failed member stayed attached. We timed the remove, zero and add sequence on the RAID 10 test array instead: a full rebuild of one 10 GiB member took 71 seconds by the kernel log.

The write-intent bitmap changes that number a lot when a disk comes back rather than being replaced, say after a loose cable or a controller reset. We failed and removed a RAID 10 member, wrote 512 MiB to the array while it was out, then returned it with --re-add instead of --add:

sudo mdadm --manage /dev/md0 --re-add /dev/sdd1

md copied only the regions the bitmap had marked dirty. The kernel log shows the recovery finishing six seconds after it started, against 71 seconds for the full rebuild of the same member:

Oct 16 13:22:25 raid01 kernel: md/raid10:md0: Disk failure on sdd1, disabling device.
                               md/raid10:md0: Operation continuing on 3 devices.
Oct 16 13:22:35 raid01 kernel: md: recover of RAID array md0
Oct 16 13:22:41 raid01 kernel: md: md0: recover done.

Use --re-add only for the same disk coming back with its superblock intact. A disk that actually failed gets replaced, partitioned and added with --add, never trusted again.

Troubleshoot mdadm assembly and alert errors

We hit each of these on the lab server while writing this guide, so the messages below are verbatim.

md: kicking non-fresh sdb1 from array!

We saw this at the first boot after the failure test. The failed member was still physically attached with its old superblock, its event counter read 26 while the array was at 54, so md left it out and assembled the array from the three current members. That’s md protecting you from stale data, not a new failure. Compare the counters with sudo mdadm --examine /dev/sdb1 | grep Events and sudo mdadm --detail /dev/md0 | grep Events, then either remove the disk or wipe it with --zero-superblock and add it back as a fresh member. The same boot also triggered the SparesMissing email, because mdadm.conf said spares=1 and the spare was now an active member.

md127 : active (auto-read-only)

The array was assembled without an ARRAY line in mdadm.conf, and the hostname in its superblock doesn’t match the current host, so md treated it as foreign. It happens after moving disks to a new server, renaming the host, or booting a rescue system. Generate the ARRAY line with mdadm --detail --scan as shown earlier, change the device to /dev/md0 if the scan printed /dev/md/raid01:0, and reboot. The read-only flag clears on the first write to the array.

mdmonitor.service was skipped because of an unmet condition check

There is no /etc/mdadm.conf, so systemd skips the monitor and you get no alerts. Create the file, add MAILADDR and restart the service. The kernel also printed md: async del_gendisk mode will be removed in future, please upgrade to mdadm-4.5+ at every boot. That’s a deprecation notice about mdadm’s userspace version, and our arrays assembled normally through six reboots with it showing.

Schedule RAID scrubs and keep the array healthy

A RAID 5 rebuild reads every sector of every surviving disk, and a RAID 1 or RAID 10 rebuild reads every sector of the failed disk’s mirror partner. Either way, that’s the worst moment to find a bad sector nobody has read in a year. A scrub reads the whole array on a schedule and catches those sectors while redundancy still exists. Rocky Linux already ships raid-check.timer enabled, set to run every Sunday at 01:00, with its settings in /etc/sysconfig/raid-check (CHECK=check, low priority). Confirm it’s scheduled:

systemctl list-timers raid-check.timer

The next run shows in the first column. On our fresh install, LAST showed the first boot time, not a completed scrub.

NEXT                             LEFT LAST                        PASSED UNIT             ACTIVATES
Sun 2026-10-18 01:00:00 UTC 1 day 11h Fri 2026-10-16 13:13:17 UTC      - raid-check.timer raid-check.service

Run one by hand after building the array, so you know how long it takes on your disks before the weekly run starts competing with real traffic:

echo check | sudo tee /sys/block/md0/md/sync_action

Progress shows in /proc/mdstat as check. Ours finished in 53 seconds for 10 GiB members. When it’s done, read the mismatch counter:

cat /sys/block/md0/md/mismatch_cnt

Zero is what you want. A non-zero count on RAID 5 means parity disagrees with data somewhere, so check the disks with smartctl before you run a repair. On RAID 1 and RAID 10, small non-zero counts can come from swap or other writes that were in flight, and they aren’t automatically a sign of a failing disk.

The rest of the routine is short, and you can do all of it from the commands already in this guide:

  • a spare in the array, or at least one on the shelf
  • a higher speed_limit_min during a rebuild when finishing matters more than latency
  • a test alert every few months with mdadm --monitor --scan --test --oneshot

The spare matters most, because the 14 hours a large disk needs to rebuild is a bad time to wait for a delivery. The test alert catches the quieter failure: a relay password that changed last spring is how a degraded array goes unnoticed for months. For anything the md layer can’t fix, the kernel md documentation and Red Hat’s RHEL 10 RAID chapter are the references we checked these steps against. Done this way, a dead disk means an email, a swap and a minute of rebuild per 10 GB, not a restore.

Keep reading

Configure Samba File Share on Debian 13 / 12 Debian Configure Samba File Share on Debian 13 / 12 Backup and Restore Linux Systems with Timeshift Debian Backup and Restore Linux Systems with Timeshift Best NAS Hard Drives for 2026: WD Red vs Seagate IronWolf, Price per TB Storage Best NAS Hard Drives for 2026: WD Red vs Seagate IronWolf, Price per TB Install OpenCloud with Docker Compose, Traefik and Collabora Containers Install OpenCloud with Docker Compose, Traefik and Collabora Nextcloud vs ownCloud vs Seafile vs Syncthing vs OpenCloud Storage Nextcloud vs ownCloud vs Seafile vs Syncthing vs OpenCloud Fix “e1000 Detected Tx Unit Hang” in Rocky / CentOS / AlmaLinux / RHEL AlmaLinux Fix “e1000 Detected Tx Unit Hang” in Rocky / CentOS / AlmaLinux / RHEL

Leave a Comment

Press ESC to close