initramfs — Initial RAM Filesystem
Technical Overview
initramfs (initial RAM filesystem) is a temporary root filesystem that the Linux kernel extracts and uses during the early stages of the boot process, before the real root filesystem can be mounted. It contains a minimal set of programs, scripts, and kernel modules needed to prepare the system for handing off to the real root.
The kernel cannot always mount the root filesystem directly. The root filesystem might live on LVM logical volumes, RAID arrays, encrypted LUKS devices, iSCSI targets, or NFS shares — all of which require drivers and tools that are not compiled into the kernel itself. initramfs provides a controlled environment in which these devices can be discovered, assembled, decrypted, and mounted, after which the real root takes over via a handoff mechanism.
initramfs is not optional on modern Linux distributions. Any system with an encrypted disk, LVM, or a driver that is built as a module (rather than compiled into the kernel) requires an initramfs to boot.
Prerequisites
- Linux kernel boot sequence (see 05-kernel-decompression.md)
- cpio archive format basics
- tmpfs and ramfs concepts
- LVM, LUKS, RAID concepts (referenced, not detailed here)
- Systemd unit basics helpful for systemd-initrd section
Historical Context
The original Unix/Linux boot model required all drivers for the root filesystem to be compiled directly into the kernel. If your root was on SCSI, you compiled SCSI support into the kernel. This worked for simple systems but created a maintenance problem: distributions needed to ship one kernel per hardware type, or a monolithic kernel with everything enabled.
The first solution was initrd (initial RAM disk, 1994), introduced in Linux 2.0. An initrd is a block device image (formatted as ext2) that the kernel mounts on /dev/ram0 as a RAM disk, then runs /linuxrc from it. The limitation: the RAM disk was fixed-size, allocated at boot time, and the block device layer added unnecessary overhead for what is essentially a small filesystem.
initramfs was introduced in Linux 2.5.x (around 2003), driven by work from H. Peter Anvin. The key insight: instead of a block device, use the kernel's own tmpfs. The kernel already has code to decompress and populate a filesystem from a cpio archive (used for the built-in initramfs). Simply extend this to load an external cpio archive from the bootloader. The result is leaner, zero-overhead, and integrated with the VFS from the start.
The legacy initrd mechanism still exists in the kernel but is rarely used. All modern distributions use initramfs.
initramfs vs initrd
This distinction is frequently confused:
| Feature | initrd (legacy) | initramfs (modern) |
|---|---|---|
| Format | ext2/ext3 image | cpio archive (optionally gzip/lz4/zstd compressed) |
| Storage in RAM | Allocated RAM disk block device | tmpfs (backed by page cache) |
| Root mechanism | Mounted on /dev/ram0 |
Extracted directly into rootfs |
| Init process | Runs /linuxrc |
Runs /init |
| Pivot root | pivot_root() syscall |
switch_root + chroot |
| Writability | Read-write ext2 | Read-write tmpfs (COW) |
| Memory reclaim | RAM disk memory not freed after pivot | Memory freed when tmpfs entries deleted |
| Kernel config | CONFIG_BLK_DEV_INITRD |
CONFIG_BLK_DEV_INITRD (same option) |
The bootloader (GRUB2) passes the initramfs to the kernel via the initrd parameter — the name is historical even when the actual format is cpio.
cpio Archive Format
initramfs uses the newc variant of the cpio archive format:
cpio newc Archive Structure:
+---------------------------+
| Header 1 (110 bytes) |
| magic: "070701" |
| ino, mode, uid, gid |
| nlink, mtime |
| filesize, devmajor, |
| devminor, namesize, |
| check (0 for newc) |
+---------------------------+
| Filename (namesize bytes) |
| Padding to 4-byte align |
+---------------------------+
| File data (filesize bytes)|
| Padding to 4-byte align |
+---------------------------+
| Header 2 ... |
| Filename 2 ... |
| File data 2 ... |
+---------------------------+
| ... (N entries) |
+---------------------------+
| TRAILER entry |
| name: "TRAILER!!!" |
+---------------------------+
The archive contains regular files, directories, symlinks, device nodes, and named pipes. Device nodes in initramfs are created via cpio metadata (no actual filesystem creation needed).
# Inspect initramfs contents
lsinitrd /boot/initramfs-$(uname -r).img # dracut (Fedora/RHEL)
# or manually:
mkdir /tmp/initrd-inspect
cd /tmp/initrd-inspect
# Find the cpio offset (may have microcode prepended)
OFFSET=$(cpio --list < /boot/initramfs-$(uname -r).img 2>&1 | \
grep -c ".*" || echo 0)
# Typically: skip any prepended early cpio (microcode)
skipcpio /boot/initramfs-$(uname -r).img | \
zstd -d | cpio -idv # for zstd-compressed initramfs
How the Kernel Extracts initramfs
The kernel has two mechanisms to load initramfs:
1. Built-in initramfs (CONFIG_INITRAMFS_SOURCE): A cpio archive embedded directly in the kernel binary at build time. Useful for embedded systems that want a single file. Extracted by init/initramfs.c:populate_rootfs() during kernel init.
2. External initramfs (normal case): Passed by bootloader via boot_params.hdr.ramdisk_image (physical address) and ramdisk_size. The kernel's do_populate_rootfs() reads the cpio from this memory range.
The extraction process in init/initramfs.c:
/* Simplified kernel initramfs extraction */
static int __init populate_rootfs(void)
{
char *err = unpack_to_rootfs(__initramfs_start,
__initramfs_size); // built-in
if (initrd_start) {
// External initramfs from bootloader
err = unpack_to_rootfs((char *)initrd_start,
initrd_end - initrd_start);
}
return 0;
}
The target of extraction is the kernel's initial rootfs (an empty tmpfs mounted at / before any user-space code runs). After extraction, the kernel runs /init from the now-populated rootfs.
initramfs Boot Flow
initramfs Boot Flow
Kernel boots, decompresses
|
| populate_rootfs() called
| Extracts cpio archive into tmpfs at /
|
v
+---------------------------+
| tmpfs root (/): |
| /init (PID 1 script) |
| /bin/ (busybox/systemd) |
| /lib/modules/ |
| /etc/ |
| /dev/ (devtmpfs) |
+---------------------------+
|
| kernel calls /sbin/init or /init as PID 1
v
/init (dracut shell script or systemd-initrd)
|
| Mounts /proc, /sys, /dev
| Loads kernel modules (via udev/modprobe)
| - Storage: nvme, ahci, virtio-blk
| - FS: ext4, xfs, btrfs, dm-crypt
| - Device mapper: dm-mod, dm-raid, dm-crypt
|
| Activates devices:
| - LVM: vgchange -a y
| - RAID: mdadm --assemble
| - LUKS: cryptsetup luksOpen
|
| Mounts real root filesystem:
| mount /dev/mapper/root /sysroot
|
| Pivot to real root:
| switch_root /sysroot /sbin/init
|
v
Real root mounted, systemd (PID 1) continues
dracut — Fedora/RHEL initramfs Generator
dracut is the initramfs generator used by Fedora, RHEL, CentOS, openSUSE, and several other distributions. It is designed to be highly modular — functionality is provided by dracut modules (not to be confused with kernel modules).
# Rebuild initramfs for current kernel
dracut --force /boot/initramfs-$(uname -r).img $(uname -r)
# Include additional kernel modules
dracut --add-drivers "virtio-blk virtio-net" --force ...
# Show what's in the initramfs
lsinitrd /boot/initramfs-$(uname -r).img
# Inspect dracut module list
dracut --list-modules
# Emergency: include ALL modules (large but universal)
dracut --force --no-hostonly /boot/initramfs.img $(uname -r)
dracut configuration: /etc/dracut.conf and /etc/dracut.conf.d/*.conf:
# /etc/dracut.conf.d/custom.conf
add_dracutmodules+=" crypt lvm "
add_drivers+=" nvme "
hostonly=yes # include only hardware on this system
compress=zstd # compression algorithm
dracut modules live in /usr/lib/dracut/modules.d/. Key modules:
- 90crypt: LUKS encrypted root
- 90lvm: LVM logical volumes
- 90mdraid: Software RAID (mdadm)
- 95nfs: NFS root
- 95iscsi: iSCSI root
- 99base: Core dracut functionality
mkinitcpio — Arch Linux
Arch Linux uses mkinitcpio, a simpler tool following Arch's design philosophy:
# /etc/mkinitcpio.conf
MODULES=(nvme)
BINARIES=()
FILES=()
HOOKS=(base udev autodetect modconf block filesystems keyboard fsck)
# HOOKS order matters:
# base - coreutils, kmod, busybox
# udev - device event daemon
# autodetect - reduce modules to current hardware
# modconf - load /etc/modprobe.d/ settings
# block - block device support
# encrypt - LUKS (add before filesystems if using crypto)
# lvm2 - LVM support
# filesystems - ext4, xfs, etc.
# keyboard - early USB keyboard
# fsck - filesystem check
# Generate initramfs
mkinitcpio -p linux # use linux preset
mkinitcpio -k 6.7.4-arch1-1 # specific kernel version
# Fallback image (all modules, not just detected hardware):
mkinitcpio -p linux-fallback
initramfs-tools — Debian/Ubuntu
Debian/Ubuntu uses initramfs-tools:
# Rebuild initramfs
update-initramfs -u # current kernel
update-initramfs -u -k all # all installed kernels
update-initramfs -c -k $(uname -r) # create new
# Configuration: /etc/initramfs-tools/
# initramfs.conf: MODULES=most|dep|netboot|list
# conf.d/: additional config snippets
# modules: list of modules to force-include
# hooks/: pre/post-init hook scripts
# scripts/: init-top, init-premount, init-bottom, etc.
systemd in initramfs (systemd-initrd)
Modern Fedora and some other distributions use systemd as the init process in initramfs (the rd.systemd dracut module). This brings systemd's dependency model, parallelism, and unit framework to the initramfs stage.
In systemd-initrd mode:
- systemd is the PID 1 in initramfs (not a shell script)
- Standard systemd units run (systemd-fsck, systemd-cryptsetup, systemd-lvm)
- initrd.target is the target that represents "initramfs is done"
- Generator scripts convert kernel cmdline params to transient units
The transition to real root is handled by systemd-switch-root.service rather than a manual switch_root call.
switch_root vs pivot_root
Two mechanisms exist to transition from initramfs to real root:
pivot_root (legacy, used with initrd):
/* pivot_root: move old root to a new mount point */
syscall(SYS_pivot_root, new_root, put_old);
/* Then: umount(put_old) to unmount initrd */
pivot_root requires the new root to be on a different filesystem from the current root. With initramfs (tmpfs), the new root often IS on a different filesystem, but pivot_root leaves the old initramfs mounted.
switch_root (correct for initramfs):
# switch_root: delete initramfs content, mount new root, exec new init
# Implemented in util-linux's switch_root utility
exec switch_root /sysroot /sbin/init
switch_root performs these operations:
1. Recursively frees all files in the current (initramfs) tmpfs
2. Calls chdir and chroot to the new root
3. Calls mount --move to move all pre-existing mounts
4. Calls exec to replace PID 1 with the new init
The critical difference: switch_root frees the initramfs memory by deleting all its files, while pivot_root keeps the old root mounted. Since initramfs is tmpfs, its pages are immediately returned to the page allocator when files are unlinked. This is important — initramfs can be 30–100MB; freeing it returns that memory to the OS.
Emergency Shell from initramfs
When root mounting fails, dracut drops to an emergency shell. This shell has access to basic tools and is the recovery environment for many boot failures.
Trigger emergency shell deliberately:
# Add to kernel cmdline:
rd.break # break before switch_root
rd.break=pre-mount # break before mounting root
rd.break=pre-pivot # break before switch_root
rd.break=cmdline # break early (read cmdline)
In dracut emergency shell:
# Examine available block devices
lsblk
ls /dev/disk/by-uuid/
# Mount root manually (e.g., if UUID changed)
mount /dev/sda2 /sysroot
# or for LUKS:
cryptsetup luksOpen /dev/sda2 cryptroot
mount /dev/mapper/cryptroot /sysroot
# Check systemd journal from initramfs (systemd-initrd)
journalctl -D /run/initramfs/etc/machine-id
# Manually continue boot
exit # dracut will retry mount + switch_root
Debugging Notes
dracut-initqueue timeout — root not found:
Most common dracut failure. Kernel cmdline root= UUID does not match any device. Causes: wrong UUID in GRUB config, renamed disk, partition table change.
# Boot with rd.break, then:
ls /dev/disk/by-uuid/
# Find correct UUID and update /etc/default/grub + grub2-mkconfig
Kernel module not found in initramfs:
# Check what's included
lsinitrd | grep -i nvme
# If missing, add to dracut.conf: add_drivers+=" nvme "
# Then: dracut --force
initramfs size too large:
Dracut in --no-hostonly mode includes all possible modules (~100MB). Use --hostonly (default) to include only drivers for hardware detected at build time. If building for a different machine, ensure drivers are listed in /etc/dracut.conf.d/.
Corrupt initramfs (cpio errors):
# Verify initramfs integrity
skipcpio /boot/initramfs-$(uname -r).img | \
zstd -d | cpio -t > /dev/null
# If this fails, rebuild: dracut --force
Security Implications
initramfs is not verified by default: Even with Secure Boot and a signed kernel, the initramfs (initrd) loaded by GRUB2 is not signature-verified in standard configurations. An attacker with access to the ESP can replace the initramfs with a malicious one that captures the LUKS passphrase or installs a backdoor before the real OS starts.
Mitigations: 1. Unified Kernel Images (UKI): Bundle kernel + initramfs + cmdline into a single signed EFI binary. The initramfs is now part of the signed unit. 2. Measured boot + TPM sealing: Measure the initramfs into PCR[9]. If the initramfs is replaced, PCR[9] changes, and TPM-sealed LUKS keys are not released. 3. systemd-stub + sd-boot: With UKIs and systemd-stub, initramfs integrity is covered by the EFI application's signature.
Secrets in initramfs:
- Never put plaintext passwords or keys in initramfs scripts or files
- LUKS key files included in initramfs (via dracut's install_items) are unencrypted inside the initramfs — anyone who can read the initramfs file can extract the key
- The dracut crypt module prompts for LUKS passphrase at boot rather than storing keys; keys stored in TPM are preferable
initramfs as attack persistence:
An attacker with root access can modify the initramfs to persist across OS reinstallation (if the boot partition is preserved). The OS reinstall wipes the root filesystem but not /boot. Always check initramfs integrity after a suspected compromise.
Performance Implications
initramfs size impact on boot time:
- Loading initramfs from NVMe: ~10ms per 100MB at NVMe speeds
- Extracting cpio to tmpfs: dominated by number of files, not size (~50ms for 1000 files)
- Module loading: each modprobe ~5ms; a typical initramfs loads 20–50 modules = 100–250ms
Compression choice:
dracut defaults to compress=zstd on recent systems. Decompression of a 30MB zstd initramfs takes ~50ms. lz4 is ~20ms decompression but 40MB size. xz is 100ms+ but ~20MB.
hostonly mode:
hostonly=yes (default) dramatically reduces initramfs size from ~100MB to ~20–40MB by including only detected hardware drivers.
Parallel initramfs loading: Some bootloaders can initiate initramfs loading while the kernel is being loaded/decompressed (pipeline parallelism). GRUB2 loads serially; EFI stub approaches may parallelize.
Failure Modes
Missing root device (most common):
Symptom: dracut-initqueue[xxx]: Warning: dracut-initqueue timeout followed by emergency shell. Cause: root=UUID=... in cmdline refers to a UUID not present.
LUKS decryption failure:
Symptom: WARNING: Failed to connect to lvmetad or device-mapper: remove ioctl failed after wrong passphrase. LUKS has a limited retry count before locking. Recovery: boot with correct passphrase.
LVM volume not activated:
If vgchange -a y in initramfs fails (e.g., due to missing RAID member disk), LVM root is inaccessible. Symptom: Volume group "centos" not found. Recovery: fix RAID, then reboot.
Filesystem corruption on root:
initramfs runs fsck before mounting root (if enabled). If fsck finds uncorrectable errors, it drops to emergency shell. Recovery: fsck -y /dev/sda2 from shell.
Modern Usage
initramfs remains central to Linux boot, with evolution in these directions:
Unified Kernel Images (UKIs):
systemd-stub wraps kernel + initramfs + cmdline + os-release into a single PE/COFF EFI binary. This UKI is signed as a whole, solving the initramfs integrity problem. Tools: ukify, systemd-stub, sd-boot.
# Build a UKI
ukify build \
--linux /boot/vmlinuz-linux \
--initrd /boot/initramfs-linux.img \
--cmdline "root=UUID=... rw quiet" \
--os-release @/etc/os-release \
--output /boot/efi/EFI/Linux/arch-linux.efi
systemd-initrd improvements: systemd in initramfs enables parallel device activation (LVM + RAID + LUKS in parallel), better error reporting via journalctl, and richer dependency modeling.
TPM2 + systemd-cryptenroll:
# Enroll TPM2 as LUKS2 unlock mechanism
systemd-cryptenroll --tpm2-device=auto /dev/sda2
# Now LUKS opens automatically on known-good boot (PCR match)
# Falls back to passphrase if PCR mismatch (tampered boot)
Future Directions
- Stateless initramfs: The Fedora stateless OS proposal would generate initramfs at runtime from a content-addressable store, ensuring reproducibility
- NixOS initramfs: Fully reproducible initramfs generated from the Nix store — same inputs always produce the same initramfs binary
- Rust dracut modules: Potential replacement of shell script hooks with compiled Rust binaries for safety and performance
- Pre-initramfs firmware loading: CPU microcode and firmware blobs (WiFi, GPU) are already loaded from a separate "early cpio" prepended to initramfs; this mechanism may extend to more firmware types
Exercises
-
initramfs Content Audit: Extract your system's initramfs with
lsinitrd(dracut) or manually viacpio. Identify: the/initscript (or symlink to systemd), which kernel modules are present, what executables are bundled, and what configuration files from/etcare included. Compare the size of the initramfs before and after enablinghostonly=yes. -
Emergency Shell Practice: Add
rd.break=pre-mountto the kernel cmdline in GRUB. Boot to the emergency shell. Mount the root filesystem manually at/sysroot. Inspect the file structure. Runswitch_root /sysroot /sbin/initto hand off. Understand what switch_root does vs justchroot. -
Custom dracut Module: Write a minimal dracut module that installs a custom hook (
/lib/dracut/hooks/pre-mount/) that runs a shell script logging the timestamp and PID to/run/custom-hook.log. Rebuild initramfs, boot, and verify the hook ran by checking the log from the real OS. -
initramfs Integrity with TPM: On a system with TPM 2.0, read PCR[9] before and after modifying initramfs (even a benign change, like adding a comment to a script). Use
tpm2_pcrread sha256:9to confirm the PCR value changed. Understand the implications for TPM-sealed LUKS keys. -
UKI Construction: Using
ukify build(systemd 253+) or manually withobjcopy, create a UKI that bundles your current kernel and initramfs. Sign it with a test MOK key usingsbsign. Install it to the ESP and add a UEFI boot entry pointing directly to it. Verify the boot works and Secure Boot accepts the signature.
References
- H. Peter Anvin's initramfs documentation:
Documentation/filesystems/ramfs-rootfs-initramfs.rst - dracut project: https://github.com/dracutdevs/dracut
- mkinitcpio wiki: https://wiki.archlinux.org/title/Mkinitcpio
- initramfs-tools: https://manpages.debian.org/initramfs-tools
init/initramfs.c— kernel initramfs extraction codeusr/Kconfig— kernel built-in initramfs config- switch_root source: https://github.com/util-linux/util-linux (switch_root.c)
- systemd-cryptenroll: https://www.freedesktop.org/software/systemd/man/systemd-cryptenroll.html
- UKI specification: https://systemd.io/UNIFIED_KERNEL_IMAGES/
- Linux kernel
Documentation/admin-guide/initrd.rst - "dracut: A generic, modular initramfs generation tool" — Harald Hoyer (RHEL developer)