Linux Software RAID (mdadm)
Managing arrays with the Linux MD subsystem
What mdadm Is
mdadm (multi-disk admin) is the userspace administration tool for the Linux kernel's MD (Multiple Device) subsystem. MD is the in-kernel driver that implements the actual RAID behaviour — mdadm just configures and monitors it.
- Implemented in
drivers/md/in the kernel tree (raid0.c,raid1.c,raid5.c, etc.) - Supports RAID 0, 1, 4, 5, 6, 10, plus linear concatenation, multipath, and faulty (for testing)
- Exposes arrays as
/dev/mdNblock devices — indistinguishable to upper layers from any other block device - Configuration state lives in superblocks on each member, not in a central controller
Relationship to other layers
mdadm arrays are block devices. You can partition them, put LVM on top, encrypt them with LUKS, or format them directly with a filesystem. MD doesn't care what sits above it.
Creating Arrays
Create a 4-disk RAID 5
# mdadm --create /dev/md0 \
--level=5 \
--raid-devices=4 \
--chunk=512 \
--bitmap=internal \
/dev/sd[abcd]1
mdadm: Defaulting to version 1.2 metadata
mdadm: array /dev/md0 started.Key flags:
--level— raid0, raid1, raid5, raid6, raid10, linear--raid-devices— number of active members--spare-devices— hot spares (see below)--chunk— stripe chunk size in KiB. 512 is a common default for spinning disks; 64–128 for SSDs. Large chunks favour sequential workloads; small chunks favour random I/O.--bitmap=internal— enable write-intent bitmap (discussed later)--metadata=1.2— superblock version (1.2 is the default)
Tip
Use partitions, not raw disks. Create a single partition spanning the disk with type fd (Linux RAID autodetect, MBR) or A19D880F-05FC-4D3B-A006-743F0F84911E (Linux RAID, GPT). This guards against accidental reuse (tools see a partition table) and lets you make the partition slightly smaller than the full disk, sidestepping the "replacement drive is 0.1% smaller" problem.
The Array Superblock
Every member disk stores a superblock describing the array: UUID, level, role of this disk, event count, checksum. On boot, the kernel scans attached disks, reads superblocks, groups them by UUID, and assembles arrays automatically.
| Version | Location on device | Notes |
|---|---|---|
| 0.90 | End of device (64 KiB aligned) | Legacy; limited to 2 TB, 28 devices. Boot-friendly (filesystem appears from byte 0). |
| 1.0 | End of device | Boot-friendly. GRUB can read the filesystem without knowing about MD (for RAID 1 mostly). |
| 1.1 | Start of device (offset 0) | Rarely used. |
| 1.2 (default) | 4 KiB into the device | Current default. Data offset follows the superblock. |
Note
Boot consideration: only 0.90 and 1.0 metadata leave the filesystem at byte 0 of the device. For a RAID 1 /boot partition that GRUB must read directly, these versions let GRUB treat one mirror member as an ordinary disk. For any other use, 1.2 is correct.
/proc/mdstat — The Status Window
A plain-text file exported by the kernel. Reflects the live state of every assembled array.
Healthy array
$ cat /proc/mdstat
Personalities : [raid1] [raid6] [raid5] [raid4]
md0 : active raid5 sdd1[3] sdc1[2] sdb1[1] sda1[0]
23441679360 blocks super 1.2 level 5, 512k chunk, algorithm 2 [4/4] [UUUU]
bitmap: 0/59 pages [0KB], 65536KB chunk
unused devices: <none>Degraded + rebuilding
$ cat /proc/mdstat
Personalities : [raid1] [raid6] [raid5] [raid4]
md0 : active raid5 sde1[4] sdc1[2] sdb1[1] sda1[0]
23441679360 blocks super 1.2 level 5, 512k chunk, algorithm 2 [4/3] [UUU_]
[===>.................] recovery = 17.8% (1391420928/7813893120) finish=423.7min speed=252618K/sec
bitmap: 0/59 pages [0KB], 65536KB chunk
unused devices: <none>Reading the status line:
[4/3]— 4 expected members, 3 currently up[UUU_]— one character per slot:U= up,_= missing/failed. Position is role index, not physical enumeration.[F]after a device name in the member list = faulty (e.g.sdc1[2](F))(S)after a device = sparerecovery = X%— sync/rebuild progress;resync,check, orreshapealso appear here- Bitmap line appears only when a write-intent bitmap is attached
Inspecting Arrays and Members
Array-level detail
# mdadm --detail /dev/md0
/dev/md0:
Version : 1.2
Creation Time : Wed Apr 9 14:22:31 2025
Raid Level : raid5
Array Size : 23441679360 (21.83 TiB 24.00 TB)
Used Dev Size : 7813893120 (7.28 TiB 8.00 TB)
Raid Devices : 4
Total Devices : 4
Persistence : Superblock is persistent
Intent Bitmap : Internal
Update Time : Mon Apr 20 11:04:58 2026
State : clean
Active Devices : 4
Working Devices : 4
Failed Devices : 0
Spare Devices : 0
Layout : left-symmetric
Chunk Size : 512K
Consistency Policy : bitmap
Name : nas:0 (local to host nas)
UUID : 8f2a1e3c:4b5d6e7f:8090a1b2:c3d4e5f6
Events : 47213
Number Major Minor RaidDevice State
0 8 1 0 active sync /dev/sda1
1 8 17 1 active sync /dev/sdb1
2 8 33 2 active sync /dev/sdc1
3 8 49 3 active sync /dev/sdd1Per-device superblock
# mdadm --examine /dev/sda1
/dev/sda1:
Magic : a92b4efc
Version : 1.2
Feature Map : 0x1
Array UUID : 8f2a1e3c:4b5d6e7f:8090a1b2:c3d4e5f6
Name : nas:0
Creation Time : Wed Apr 9 14:22:31 2025
Raid Level : raid5
Raid Devices : 4
Avail Dev Size : 15627786240 sectors (7.28 TiB 8.00 TB)
Array Size : 23441679360 KiB (21.83 TiB 24.00 TB)
Data Offset : 264192 sectors
Super Offset : 8 sectors
State : clean
Device UUID : 1a2b3c4d:5e6f7a8b:9c0d1e2f:3a4b5c6d
Internal Bitmap : 8 sectors from superblock
Update Time : Mon Apr 20 11:04:58 2026
Checksum : e7d1a92b - correct
Events : 47213
Layout : left-symmetric
Chunk Size : 512K
Device Role : Active device 0
Array State : AAAA ('A' == active, '.' == missing, 'R' == replacing)Tip
Event counter matters. When assembling an array from disconnected members, MD picks the group with the highest event count and considers the rest stale. Comparing Events across --examine output is how you diagnose "why won't this array assemble?"
Persistent Configuration
The superblocks are sufficient to assemble an array, but mdadm.conf tells the system which arrays to look for, what to name them, and where to send alerts.
| Distro | Path |
|---|---|
| Debian / Ubuntu | /etc/mdadm/mdadm.conf |
| RHEL / Fedora / Arch | /etc/mdadm.conf |
Generating the config from running arrays
# mdadm --detail --scan >> /etc/mdadm/mdadm.conf
# cat /etc/mdadm/mdadm.conf
DEVICE partitions
HOMEHOST <system>
MAILADDR root
ARRAY /dev/md0 metadata=1.2 name=nas:0 UUID=8f2a1e3c:4b5d6e7f:8090a1b2:c3d4e5f6Warning
/boot lives on an MD array, mdadm.conf must be baked into the initramfs so the kernel can assemble the array before pivoting to the real root. Forgetting this step breaks boot.
# update-initramfs -u # Debian/Ubuntu
# dracut -f # RHEL/FedoraMonitoring and Alerting
MD can push events to a monitoring daemon which sends email on failure. Without this, a failed disk sits unnoticed until the second disk fails.
One-shot manual invocation
# mdadm --monitor --daemonise --mail=root --delay=300 /dev/md0Configured via mdadm.conf + systemd
# /etc/mdadm/mdadm.conf
MAILADDR ops@example.com
MAILFROM nas-root@example.com
# systemctl enable --now mdmonitor.serviceEvents reported: Fail, FailSpare, SpareActive, NewArray, DegradedArray, MoveSpare, TestMessage.
Tip
Test the alert path first. mdadm --monitor --test --oneshot --mail=ops@example.com /dev/md0 sends a test email. Silent monitoring is worse than no monitoring because it produces false confidence.
Hot Spares
A spare sits idle until an active member fails; MD then kicks off rebuild automatically onto the spare.
Create with a spare
# mdadm --create /dev/md0 \
--level=5 --raid-devices=4 --spare-devices=1 \
/dev/sd[abcd]1 /dev/sde1Add a spare to an existing array
# mdadm /dev/md0 --add-spare /dev/sdf1Global spares (spare-group)
A single spare can be pooled across multiple arrays. Tag each array with the same spare-group name in mdadm.conf; MD's monitor daemon moves spares between arrays on demand.
# /etc/mdadm/mdadm.conf
ARRAY /dev/md0 spare-group=pool1 UUID=...
ARRAY /dev/md1 spare-group=pool1 UUID=...Replacing a Failed Disk
- Identify the failed disk.
/proc/mdstatshows which slot ([UU_U]),mdadm --detailgives the device node,dmesghas the kernel error log. Cross-check withsmartctl -a /dev/sdXand the disk serial vials -l /dev/disk/by-id/.console # ls -l /dev/disk/by-id/ | grep sdc ata-WDC_WD80EFAX-68LHPN0_7HK5L0RC -> ../../sdc - Mark as failed (if not already). MD may still show the drive as active if it's flaky but not hard-failed.
console # mdadm /dev/md0 --fail /dev/sdc1 - Remove from the array.
console # mdadm /dev/md0 --remove /dev/sdc1 - Physically replace the disk. With hot-swap backplanes, pull and insert. Without, shut down, replace, and boot.
- Partition the new disk identically. Copy the partition table from a surviving member.
console # MBR # sfdisk -d /dev/sda | sfdisk /dev/sdc # GPT (sgdisk is safer for GPT) # sgdisk --replicate=/dev/sdc /dev/sda # sgdisk --randomize-guids /dev/sdc - Add the new partition to the array. If a hot spare is configured, MD may rebuild automatically onto the spare; this
--addmakes the replacement a new spare.console # mdadm /dev/md0 --add /dev/sdc1 - Watch the rebuild.
console # watch -n1 'cat /proc/mdstat'
Note
During rebuild the array is degraded. A second disk failure in the same parity group destroys the array. For RAID 5 this is the exact URE risk from the levels page; for big arrays prefer RAID 6 or RAID 10 so this window isn't catastrophic.
Rebuild Speed Tuning
MD throttles resync/rebuild/check so it doesn't starve foreground I/O. Two knobs in /proc/sys/dev/raid/:
| Sysctl | Default | Meaning |
|---|---|---|
speed_limit_min |
1000 KB/s | Minimum guaranteed rebuild bandwidth, even under load |
speed_limit_max |
200000 KB/s | Ceiling when the array is idle |
Push rebuild faster on an idle array
# echo 500000 > /proc/sys/dev/raid/speed_limit_max
# echo 50000 > /proc/sys/dev/raid/speed_limit_min
# Persist via sysctl.d:
# cat > /etc/sysctl.d/90-mdadm.conf <<EOF
dev.raid.speed_limit_min = 50000
dev.raid.speed_limit_max = 500000
EOFStripe cache size (/sys/block/mdN/md/stripe_cache_size, for RAID 5/6) controls how many stripes are held in memory during writes. Default 256; bumping to 4096 or 8192 often helps RAID 5/6 write throughput at the cost of RAM (stripe_cache_size × chunk × (N−1) bytes).
Scrubbing — Periodic Integrity Checks
Scrubbing reads every stripe and verifies that data and parity agree (or that mirrors match). Without scrubbing, a silent read error on an otherwise-unused sector only gets noticed during rebuild — by which time it's too late.
| Action | Command | What it does |
|---|---|---|
check |
echo check > /sys/block/md0/md/sync_action |
Reads all members, compares data vs parity/mirrors. Increments mismatch_cnt but does not modify. Safe on live arrays. |
repair |
echo repair > /sys/block/md0/md/sync_action |
Rewrites parity from the data. Only safe if data is known good — blindly repairing after a power event can bake in corruption. |
idle |
echo idle > /sys/block/md0/md/sync_action |
Aborts the current check/repair. |
Check mismatch count after a check pass
# cat /sys/block/md0/md/mismatch_cnt
0On Debian/Ubuntu, /etc/cron.d/mdadm runs a monthly checkarray on the first Sunday. On RHEL, mdcheck timers do the same. Non-zero mismatch_cnt after a check is a red flag worth investigating, especially on RAID 1 where it often indicates a flaky member.
Growing and Reshaping Arrays
MD supports online reshape — adding disks, changing RAID level, expanding after a disk swap — without taking the array offline. It is slow (hours to days for a full reshape) and risky.
Add a 5th disk to a 4-disk RAID 5
# mdadm /dev/md0 --add /dev/sde1
# mdadm --grow /dev/md0 --raid-devices=5 \
--backup-file=/root/md0-reshape.bakAfter swapping all disks for larger ones, grow to full size
# mdadm --grow /dev/md0 --size=max
# resize2fs /dev/md0 # or xfs_growfs, or pvresize, etcWarning
Back up first. Reshape rewrites the data layout on every disk while the array is live. A power event or disk failure mid-reshape without a --backup-file can leave the array unrecoverable. Always: (1) full backup, (2) UPS, (3) --backup-file, (4) verify the reshape completes before doing anything else with the array.
Write-Intent Bitmap
A bitmap that tracks which regions of the array have pending writes. On unclean shutdown or a disk re-adding after brief disconnection, MD only has to resync the dirty regions instead of the entire array — typically a few GB instead of many TB.
| Option | Command | Trade-offs |
|---|---|---|
| Internal | --bitmap=internal |
Stored in the superblock area on each member. Easy, portable. Small write overhead since every write updates the bitmap on every member. |
| External file | --bitmap=/path/file |
Stored in a file on a separate filesystem. Rarely used. |
| None | --bitmap=none |
Every unclean event requires full resync. Only consider for write-heavy benchmarks or arrays with very short resync times. |
Add a bitmap to an existing array
# mdadm --grow --bitmap=internal /dev/md0
# mdadm --grow --bitmap=none /dev/md0 # remove itTip
Bitmap chunk size (--bitmap-chunk=) controls bitmap granularity. Larger chunks = smaller bitmap, more data to resync per dirty bit. The default (64 MiB) is a reasonable compromise.
Journal Device — Closing the Write Hole
For RAID 4/5/6, MD supports attaching a dedicated journal device (typically a small, fast SSD with power-loss protection). Stripe updates are written to the journal first, then committed to the array. On crash, the journal is replayed — no partial stripe, no write hole.
Create an array with a journal
# mdadm --create /dev/md0 \
--level=5 --raid-devices=4 \
--write-journal=/dev/nvme0n1p1 \
/dev/sd[abcd]1Add a journal to an existing RAID 5
# mdadm --grow /dev/md0 --add-journal=/dev/nvme0n1p1Warning
Journal is a SPOF. If the journal SSD fails, writes stop until it's replaced. In production, the journal device should itself be a mirrored pair (e.g. md1 = RAID 1 of two NVMe partitions, then md0 uses /dev/md1 as its journal).
Layered Stacks — Common Patterns
MD rarely stands alone. Typical compositions:
Pattern A — Classic NAS
Simple, predictable. Good for single-purpose bulk storage.
Pattern B — Encrypted with LVM flexibility
RAID first, encryption once on the aggregate device (single key, parallelism across CPU cores), LVM for flexible volume management. Standard on Debian/Ubuntu encrypted installs with RAID.
Pattern C — LVM-managed RAID (no mdadm)
LVM can create RAID directly using the dm-raid kernel target (same MD code underneath). Unified management via lvcreate --type raid5. Fewer moving parts for greenfield LVM deployments.
Pattern D — RAID direct to FS (no LVM)
Common for /boot and simple mirrored system drives. Minimum layers, maximum simplicity.
Tip
ZFS and btrfs bypass this stack entirely. They own disks directly and integrate RAID, volume management, and checksummed filesystem in one layer. If you want end-to-end integrity, pick ZFS/btrfs instead of mdadm; don't stack them on top.
Stopping, Assembling, Zeroing
Stop (unassemble) an array
# umount /mnt/data
# mdadm --stop /dev/md0Reassemble from members
# mdadm --assemble --scan # use mdadm.conf
# mdadm --assemble /dev/md0 /dev/sd[abcd]1 # explicitDestroy — wipe superblocks so disks can be reused
# mdadm --stop /dev/md0
# mdadm --zero-superblock /dev/sd[abcd]1Warning
Without --zero-superblock, the next boot may auto-assemble "ghost" arrays from the old member superblocks, especially if the disks get repurposed into a new array. Always zero superblocks when decommissioning.