How to Rebuild initramfs on RHEL with dracut

Deepak Prasad
Tested on RHEL 10.2
Package dracut 107-8.el10_2
Applies to RHEL, Rocky Linux, AlmaLinux, Oracle Linux, CentOS Stream, Fedora
Privilege sudo or root
Scope Regenerate the initramfs for one kernel or every installed kernel, add storage and network drivers, inspect an image, and repack a custom initrd by hand. Does not cover the Debian-family update-initramfs workflow.
Related guides Fix cpio premature end of archive and extract initramfs
Linux boot process explained step by step
Configure GRUB2 in RHEL
Update the kernel on RHEL and CentOS
Remove old kernels with dnf

The initramfs is the small archive the kernel unpacks into memory before your real root filesystem exists. It carries just enough — storage drivers, LVM and multipath tooling, a minimal systemd — to find the root device and hand control over to it. When that archive falls out of step with the machine it has to boot, the kernel loads and then drops you into an emergency shell, because nothing inside the image knows how to reach /.

You will see it called initrd too, including in the filenames under /boot and in most tooling. The original initrd was a compressed block device the kernel mounted as a real filesystem; initramfs replaced it with a cpio archive unpacked straight into tmpfs. Only the newer mechanism is in use today, but the older name stuck, so treat the two words as interchangeable when you read documentation.

dracut is the tool that builds this image on RPM-based distributions. Below I regenerate it for the running kernel, then for a kernel I am not running, add a network driver to prove the change landed, and finish with the manual unpack-and-repack workflow you need when you are customising an installer initrd rather than a system one. I ran every command on a live host, staged each rebuild to a temporary file so the bootable image was never at risk, and kept the real terminal output so you can compare it against your own machine.


When you need to rebuild the initramfs

Package updates usually handle this for you. Installing a kernel triggers a fresh image for that kernel, and a dracut update regenerates what it needs, so most systems never require a manual rebuild.

You have to do it yourself when boot-critical configuration changes after the image was built:

  • You moved the root filesystem, changed the LVM layout, or edited /etc/fstab in a way that affects /
  • You added a storage controller or a RAID card the current image has no driver for
  • You turned FIPS mode, LUKS encryption, or multipath on or off
  • You need a driver the installer never detected, typically on new hardware
  • An image in /boot is truncated or was deleted
IMPORTANT
Nothing you do here affects the running system. The kernel reads the initramfs only at boot, so a broken image stays invisible until you reboot. Keep a backup of the working image and make sure a rescue boot entry exists before you restart.

Inspect the current initramfs before you rebuild it

Every path under /boot is named after a kernel version, so start by asking which kernel you are actually running:

bash
uname -r
output
6.12.0-211.42.1.el10_2.x86_64

That string is the argument you will pass to dracut later, and the suffix in every filename below.

Now list the images the system currently has:

bash
ls -lh /boot/initramfs-*.img
output
-rw-------. 1 root root 234M Aug  3 11:39 /boot/initramfs-0-rescue-23b7a5ba4a464d768c37c2b2990e7d06.img
-rw-------. 1 root root  59M Aug  3 11:42 /boot/initramfs-6.12.0-211.42.1.el10_2.x86_64+debug.img
-rw-------. 1 root root  54M Aug  3 11:43 /boot/initramfs-6.12.0-211.42.1.el10_2.x86_64.img

Three images, and the difference matters. The last one belongs to the running kernel and is the one you normally rebuild. The +debug variant belongs to a separate debug kernel. The rescue image is deliberately huge because it is built with every driver instead of a host-only subset, and it is your way back in if a rebuild goes wrong — leave it alone.

lsinitrd reads an image without extracting it, which tells you how the current one was assembled:

bash
lsinitrd /boot/initramfs-$(uname -r).img | head -28
output
Image: /boot/initramfs-6.12.0-211.42.1.el10_2.x86_64.img: 54M
========================================================================
Early CPIO image
========================================================================
drwxr-xr-x   2 root     root            0 Jun 25 05:30 .
-rw-r--r--   1 root     root            2 Jun 25 05:30 early_cpio
drwxr-xr-x   2 root     root            0 Jun 25 05:30 kernel
drwxr-xr-x   2 root     root            0 Jun 25 05:30 kernel/x86
drwxr-xr-x   2 root     root            0 Jun 25 05:30 kernel/x86/microcode
-rw-r--r--   1 root     root       141312 Jun 25 05:30 kernel/x86/microcode/GenuineIntel.bin
========================================================================
Version: dracut-107-8.el10_2

Arguments:  -f --kver '6.12.0-211.42.1.el10_2.x86_64'

dracut modules:
bash
systemd
fips
fips-crypto-policies
systemd-ask-password
systemd-battery-check
systemd-cryptsetup
systemd-initrd
systemd-journald

Three things in that header are worth reading carefully. The Early CPIO image block means this file is really two archives concatenated: an uncompressed cpio holding CPU microcode, followed by the compressed main archive. The Version line names the dracut build that produced it. The Arguments line records the command-line arguments used to build the image; it does not necessarily expose settings inherited from /etc/dracut.conf or /etc/dracut.conf.d/.

That distinction matters when an image contains something you cannot account for, so read the configuration files as well:

bash
grep -rv '^#' /etc/dracut.conf /etc/dracut.conf.d/

On this host the command prints nothing, so every option in the images below came from the command line. Anything it does list — an add_drivers+= entry, for instance — shapes every future rebuild without ever appearing in Arguments.

It also helps to see the kernel command line dracut derives from the current disk layout, since a wrong value here is a common cause of failed boots:

bash
dracut --print-cmdline
output
rd.lvm.lv=rhel/root   rd.lvm.lv=rhel/swap  rd.driver.pre=dm_multipath  resume=/dev/mapper/rhel-swap root=/dev/mapper/rhel-root rootfstype=xfs rootflags=rw,relatime,seclabel,attr2,inode64,logbufs=8,logbsize=32k,noquota

This host boots from LVM, so dracut asks for the rhel/root and rhel/swap logical volumes by name. If your root device changed, this is where you would notice that the image is about to look for the wrong one.

One last check before writing anything. /boot is a small separate partition on most installations, and a rebuild needs room for a second copy of the image while it works:

bash
df -h /boot
output
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda2       2.0G  518M  1.5G  27% /boot

1.5 GB free against a 54 MB image is plenty. If yours is nearly full, clear space first — a rebuild that runs out of room mid-write leaves a truncated image behind.


Rebuild the initramfs with dracut

The rebuild itself is one command, but the order of operations around it is what keeps a bad image from costing you a reboot.

Back up the image you are about to overwrite

Copy the working image somewhere off /boot so you are not competing for space on the partition you are writing to:

bash
cp -p /boot/initramfs-$(uname -r).img /var/tmp/initramfs-$(uname -r).img.bak

cp prints nothing when it succeeds, so confirm the copy actually exists and matches the size you saw earlier:

bash
ls -lh /var/tmp/initramfs-$(uname -r).img.bak
output
-rw-------. 1 root root 54M Aug  3 11:43 /var/tmp/initramfs-6.12.0-211.42.1.el10_2.x86_64.img.bak

The -p flag preserved the original timestamp, which is why this still reads Aug 3 rather than today — a useful signal that you copied the old image and not a new one.

Build to a temporary file first

Given a target path, dracut writes there instead of /boot. That gives you a full dry run: the same build, none of the risk.

bash
dracut /tmp/initramfs-test.img "$(uname -r)"

The command produces no output at all when it works, and took about a minute on my VM. Silence is the success case here, so check the file it was supposed to create:

bash
ls -lh /tmp/initramfs-test.img
output
-rw-------. 1 root root 56M Aug 14 21:42 /tmp/initramfs-test.img

56 MB against the 54 MB original is a normal difference — the module set on disk has moved on since the original image was built.

It is worth asking file what it thinks the result is, because the answer surprises people:

bash
file /tmp/initramfs-test.img
output
/tmp/initramfs-test.img: ASCII cpio archive (SVR4 with no CRC)

file reports a plain cpio archive rather than a compressed one, because it only looks at the start of the file — and the start is that uncompressed microcode cpio. The compressed archive sits further in. Remember this: it is the reason the manual extraction commands you may have seen fail on modern images.

Watch the build with --verbose

When a build fails, or you want to know which modules are being pulled in, --verbose narrates the whole process. Adding --force lets it overwrite the file from the previous run:

bash
dracut --force --verbose /tmp/initramfs-verbose.img "$(uname -r)"
output
dracut[I]: *** Including module: shell-interpreter ***
dracut[I]: *** Including module: shutdown ***
dracut[I]: *** Including modules done ***
dracut[I]: *** Installing kernel module dependencies ***
dracut[I]: *** Installing kernel module dependencies done ***
dracut[I]: *** Resolving executable dependencies ***
dracut[I]: *** Resolving executable dependencies done ***
dracut[I]: *** Hardlinking files ***
dracut[I]: *** Generating early-microcode cpio image ***
dracut[I]: *** Constructing GenuineIntel.bin ***
dracut[I]: *** Store current command line parameters ***
dracut[I]: *** Creating image file '/tmp/initramfs-verbose.img' ***
dracut[I]: Using auto-determined compression method 'pigz'
dracut[I]: *** Creating initramfs image file '/tmp/initramfs-verbose.img' done ***

The last two lines answer a question people often guess wrong: dracut picked pigz, so this image is gzip-compressed. Older guides assume xz, and the compression dracut chooses varies with the distribution and what is installed — never hardcode it.

Replace the image for the running kernel

Once the staged build succeeds, point the same command at the real path. This is the one step that touches /boot, and --force is required because the file already exists:

bash
dracut --force /boot/initramfs-$(uname -r).img "$(uname -r)"

It stays silent for about a minute again, then returns. Ask lsinitrd to confirm the header changed:

bash
lsinitrd /boot/initramfs-$(uname -r).img | sed -n '9,14p'
output
drwxr-xr-x   2 root     root            0 Jun 25 05:30 kernel/x86/microcode
-rw-r--r--   1 root     root       141312 Jun 25 05:30 kernel/x86/microcode/GenuineIntel.bin
========================================================================
Version: dracut-107-8.el10_2

Arguments:  --force

The Arguments line now reads --force instead of the original -f --kver '…', which is proof this image came from your rebuild rather than the installer. If you are running a bare dracut -f with no paths at all, that is the same operation — it defaults to the running kernel and its image in /boot.

Because you overwrote the file in place, the bootloader needs no attention: GRUB entries reference a path such as /boot/initramfs-<kernel>.img, and that path has not changed. Bootloader configuration only comes into play when you write the image under a different filename or add a new boot entry for it.

Rebuild for a kernel you are not running

Rescue work and post-install fixes usually mean building an image for a kernel other than the live one. dracut accepts any kernel version that has a module tree installed, so list them first:

bash
ls -1 /lib/modules/
output
6.12.0-211.42.1.el10_2.x86_64
6.12.0-211.42.1.el10_2.x86_64+debug

Pass one of those strings as the second argument instead of $(uname -r). I am sending the output to /tmp here so nothing in /boot changes:

bash
dracut --force /tmp/initramfs-debug.img 6.12.0-211.42.1.el10_2.x86_64+debug

No output once more, and this build ran slightly longer than the one for the running kernel. Compare the size against the earlier image to see why:

bash
ls -lh /tmp/initramfs-debug.img
output
-rw-------. 1 root root 62M Aug 14 21:53 /tmp/initramfs-debug.img

The debug kernel's image came out at 62 MB against 56 MB for the running kernel, because a debug kernel ships a bigger module set. To write it where the bootloader expects it, swap the temporary path for /boot/initramfs-6.12.0-211.42.1.el10_2.x86_64+debug.img.

Rebuild every image with --regenerate-all

After a change that affects all kernels — switching on FIPS, altering the root device — you want every image rebuilt rather than just one. dracut --regenerate-all --force rebuilds the initramfs images for the kernel versions found on the system.

I have deliberately not run that on this host. Because it replaces multiple boot images at once, back up the images you depend on and confirm that /boot has sufficient free space before running it. When you are troubleshooting, rebuilding one kernel at a time makes a failure much easier to isolate.


Add storage or network drivers to the initramfs

By default dracut builds in host-only mode: it includes drivers for hardware it can see right now and leaves out everything else. That keeps the image small, and it is also why an image can boot one machine and fail on another. --add-drivers forces named kernel modules in regardless of what is detected.

First confirm the module you want is actually absent from the current image, so you can tell afterwards that your flag did something:

bash
lsinitrd /boot/initramfs-$(uname -r).img | grep -c 'e1000e'
output
0

Zero matches — this VM has no Intel gigabit NIC, so host-only detection correctly left the driver out. That is the baseline.

Now rebuild with the driver requested. Give the module name as it appears under /lib/modules, without the .ko suffix:

bash
dracut --force --add-drivers "e1000e" /tmp/initramfs-drivers.img "$(uname -r)"

Silent again, so verify by searching the new image for the same string that returned nothing before:

bash
lsinitrd /tmp/initramfs-drivers.img | grep 'e1000e'
output
Arguments:  --force --add-drivers 'e1000e'
drwxr-xr-x   2 root     root            0 Jun 25 05:30 usr/lib/modules/6.12.0-211.42.1.el10_2.x86_64/kernel/drivers/net/ethernet/intel/e1000e
-rw-r--r--   1 root     root       135636 Jun 25 05:30 usr/lib/modules/6.12.0-211.42.1.el10_2.x86_64/kernel/drivers/net/ethernet/intel/e1000e/e1000e.ko.xz

The driver is in, and the Arguments line recorded the flag that put it there. Pass several modules as one space-separated string when you need more than one, and use --add rather than --add-drivers when you want a whole dracut module such as network or multipath instead of an individual kernel driver.

HINT
To make a driver survive future kernel updates, put the flag in a configuration file instead of typing it each time. A file such as /etc/dracut.conf.d/99-local.conf containing add_drivers+=" e1000e " is picked up by every later rebuild, including the automatic ones. Keep the spaces inside the quotes — dracut expects them.

Inspect what ended up inside the image

Listing an image is lsinitrd with no flags, but two options make it much more useful for answering specific questions.

Sorting by size tells you what is taking up the space when an image grows unexpectedly:

bash
lsinitrd -s /boot/initramfs-$(uname -r).img | head -12
output
Image: /boot/initramfs-6.12.0-211.42.1.el10_2.x86_64.img: 54M
========================================================================
Early CPIO image
========================================================================
drwxr-xr-x   2 root     root            0 Jun 25 05:30 .
drwxr-xr-x   2 root     root            0 Jun 25 05:30 kernel
drwxr-xr-x   2 root     root            0 Jun 25 05:30 kernel/x86
drwxr-xr-x   2 root     root            0 Jun 25 05:30 kernel/x86/microcode
-rw-r--r--   1 root     root            2 Jun 25 05:30 early_cpio
-rw-r--r--   1 root     root       141312 Jun 25 05:30 kernel/x86/microcode/GenuineIntel.bin
========================================================================
Version: dracut-107-8.el10_2

The early cpio section is listed first and separately, which is a reminder that the microcode blob is not part of the compressed archive proper.

To read one file out of an image without unpacking it, use -f. The argument order is the part that trips people up — the filename comes first, then the image:

bash
lsinitrd -f usr/lib/initrd-release /boot/initramfs-$(uname -r).img
output
NAME="Red Hat Enterprise Linux"
RELEASE_TYPE=stable
ID="rhel"
ID_LIKE="centos fedora"
VERSION_ID="10.2"
PLATFORM_ID="platform:el10"
CPE_NAME="cpe:/o:redhat:enterprise_linux:10.2"

That file records which release the image was built on, which is handy when you are staring at an image recovered from another machine. Give the real path rather than a symlink — asking for etc/initrd-release returns nothing, because inside the archive it is only a link to usr/lib/initrd-release.


Unpack and repack a custom initrd by hand

Everything so far let dracut assemble the image. There is one situation where you still take an image apart yourself: customising an installer initrd from an ISO, where you want to inject a driver update or a udev rule into initrd.img before PXE or DVD boot. The full extraction walkthrough, including the cpio premature end of archive error, covers the failure modes in more depth.

Reach for this only when the change has to happen in early boot. If you are patching the installer itself rather than its driver set, building an Anaconda updates.img overlays the runtime at a later stage and spares you repacking a boot image at all.

Older guides tell you to pipe the image through xz. Here is what that advice does on a current image:

bash
xz -dc < /boot/initramfs-$(uname -r).img | head -2
output
xz: (stdin): File format not recognized

The command fails because it hits that uncompressed microcode cpio at the front, not an xz stream — the same two-part structure file hinted at earlier. Guessing the compression is the wrong approach. lsinitrd --unpack locates and decompresses the main initramfs archive automatically, but it does not extract the early microcode cpio. Use --unpackearly separately when you need that portion of the image. Work in an empty directory, because it extracts into the current one:

bash
mkdir -p /tmp/initramfs-unpack && cd /tmp/initramfs-unpack

With the directory in place, unpack the image into it:

bash
lsinitrd --unpack /boot/initramfs-6.12.0-211.42.1.el10_2.x86_64.img

This one is also quiet on success, so list what appeared:

bash
ls -l
output
lrwxrwxrwx.  1 root root    7 Aug 14 21:45 bin -> usr/bin
drwxr-xr-x.  2 root root   74 Aug 14 21:45 dev
drwxr-xr-x. 19 root root 4096 Aug 14 21:45 etc
lrwxrwxrwx.  1 root root   23 Aug 14 21:45 init -> usr/lib/systemd/systemd
lrwxrwxrwx.  1 root root    7 Aug 14 21:45 lib -> usr/lib
drwxr-xr-x.  2 root root    6 Aug 14 21:45 proc
lrwxrwxrwx.  1 root root    8 Aug 14 21:45 sbin -> usr/sbin
-rwxr-xr-x.  1 root root 4817 Aug 14 21:45 shutdown
drwxr-xr-x.  2 root root    6 Aug 14 21:45 sysroot
drwxr-xr-x.  8 root root   81 Aug 14 21:45 usr
drwxr-xr-x.  4 root root   51 Aug 14 21:45 var

A miniature root filesystem, with init pointing at systemd. This is where you would drop in a udev rule under usr/lib/udev/rules.d/ or add a vendor module — without moving any of the existing directories, since the boot scripts depend on the layout.

When the edits are done, repack the tree as a cpio archive and compress it:

bash
find . | cpio --quiet -o -H newc | gzip -9 > /tmp/initramfs-custom.img

-H newc selects the archive format the kernel expects, and the pipeline is silent unless cpio complains. Check what you produced:

bash
file /tmp/initramfs-custom.img
output
/tmp/initramfs-custom.img: gzip compressed data, max compression, from Unix, original size modulo 2^32 121208832

A single gzip-compressed archive, and that word single is the catch: repacking this way drops the early microcode section, because you only unpacked and rebuilt the main archive. For an installer initrd that is usually fine. For a system image it is not, which is the practical argument for letting dracut build anything that has to boot a real machine.

WARNING
Do not copy a hand-repacked archive over an image in /boot and reboot without a tested fallback. It loses the early microcode section, and any mistake in the directory layout ends in an emergency shell. Keep the backup you made earlier and a working rescue entry.

Troubleshooting

Symptom Likely cause Fix
No space left on device partway through a rebuild /boot filled up while writing a second copy of the image Check df -h /boot, remove superseded kernels, then rebuild
xz: (stdin): File format not recognized The image begins with an uncompressed microcode cpio, and the compression may not be xz anyway Use lsinitrd --unpack instead of guessing the compressor
Boot stops in an emergency shell after a rebuild Host-only mode left out a driver the root device needs Boot the rescue entry and rebuild with --add-drivers, or with --no-hostonly for a portable image
lsinitrd -f prints nothing Arguments reversed, or the path inside the image is a symlink Put the filename before the image, and target the real path
The new image is noticeably larger than the old one Extra drivers were added, or host-only pruning is off Compare the Arguments line of both images with lsinitrd
Rebuild works but the change is missing at boot The image was written under a filename the bootloader does not load Confirm the path in the boot entry matches the file you wrote

Summary

Rebuilding an initramfs comes down to one command, dracut, and the discipline around it. Point it at a temporary path and it performs the whole build with nothing at stake; point it at the file in /boot with --force and it replaces the image the bootloader loads. Either way it stays silent on success, so lsinitrd is how you actually confirm the result — the Arguments line tells you which command line produced the image in front of you, and the files under /etc/dracut.conf.d/ tell you what else was folded in without being recorded there.

The mistake worth avoiding is assuming the image is a single compressed archive. It is not: a small uncompressed cpio holding CPU microcode sits in front of the compressed part, which is why file reports a plain cpio archive and why piping the image through xz fails outright. That structure is also why hand-repacking suits installer initrds and little else — --unpack deliberately leaves the microcode section alone, so a manual repack quietly drops it. Anything that has to boot a real machine should be built by dracut.

If you are working on hardware the installer did not fully detect, --add-drivers with a verification pass through lsinitrd is the loop to use, and moving that flag into /etc/dracut.conf.d/ makes it survive the next kernel update. Before any reboot that depends on a new image, keep the backup copy you made and confirm a rescue entry is still in the boot menu. The initramfs is invisible while the system is up and unforgiving the moment you restart, so the cheap precautions are the ones that pay.


References

Omer Cakmak

Linux Administrator

Highly skilled at managing Debian, Ubuntu, CentOS, Oracle Linux, and Red Hat servers. Proficient in bash scripting, Ansible, and AWX central server management, he handles server operations on OpenStack, KVM, Proxmox, and VMware.

  • Debian
  • Ubuntu
  • Linux
  • Red Hat Enterprise Linux
  • Shell Script
  • System Administration