MicroSD Boot Partition with NVMe Root Filesystem on Raspberry Pi 5
20 Oct 2025I run NixOS on my Raspberry Pi 5. For a while I’ve been running everything off a microSD card, but I wanted to migrate to an NVMe drive connected through a M.2 HAT via the PCIe slot for better performance and reliability.
I currently use the now deprecated raspberry-pi-nix flake to build an image for my RPi5. While I’d like to migrate to the actively maintained nixos-raspberrypi flake, I haven’t gotten around to it yet. When using u-boot with this flake, it’s currently unable to boot directly off NVMe on the Pi 5, so a hybrid boot approach with the microSD card is necessary.
I found it’s possible to set up a hybrid boot configuration - boot from the microSD card initially, then mount the NVMe drive as the root filesystem. This gives me the performance benefits of NVMe while working around the u-boot limitations.
Updating the firmware
Before attempting the hybrid boot setup, I updated my Raspberry Pi 5’s firmware to the latest version. Newer firmware versions have better NVMe support and handle drive detection more reliably. For example, this issue tracks recently released improvements for booting off NVMe drives connected through PCIe switches.
I flashed Raspberry Pi OS to a separate microSD card, booted from it, and updated the bootloader firmware directly from the command line:
$ sudo rpi-eeprom-update -a
Alternatively, the bootloader can be updated through the interactive menu:
$ sudo raspi-config
Then navigate to Advanced Options → Bootloader Version → Latest.
After the update completed, I rebooted and verified the firmware version:
$ sudo rpi-eeprom-update
Once confirmed the firmware was up to date, I proceeded with setting up the hybrid boot configuration.
The hybrid boot approach
The idea is simple: the Raspberry Pi 5 reads the boot partition from the microSD card to load the kernel and initial boot configuration, but then switches to using the NVMe drive for the actual root filesystem.
The boot process works like this:
- RPi5 firmware reads the FAT32 boot partition on the microSD card (
/dev/mmcblk0p1
). - It loads the kernel and reads
cmdline.txt
which specifies the root partition. - The root partition specified in
cmdline.txt
points to the NVMe drive instead of the microSD card. - System boots with NVMe as the root filesystem.
Setting up the hybrid boot
Step 1: Prepare cmdline.txt for delayed boot
First, I needed to give the system enough time to detect the NVMe drive during boot. I mounted the
boot partition and modified cmdline.txt
before cloning:
$ sudo mount /dev/mmcblk0p1 /mnt
$ sudo vi /mnt/cmdline.txt
I added rootwait rootdelay=30
to the end of the existing parameters. The rootdelay=30
gives the
system 30 seconds to detect and initialize the NVMe drive before attempting to mount the root partition.
Once done, I unmounted the partition:
$ sudo umount /mnt
Step 2: Clone the microSD card to NVMe
Next, I cloned the entire microSD card contents to the NVMe drive (dangerous! this will wipe off the NVMe drive).
I switched to a root shell to avoid permission issues with block device operations:
$ sudo su
# pv /dev/mmcblk0 > /dev/sda
This took a while since it’s copying everything bit-by-bit to the NVMe drive. The result is an exact duplicate of the microSD card on the NVMe.
Step 3: Modify partition identifiers to avoid conflicts
Here’s the tricky part - both the microSD card and NVMe now have identical partition UUIDs and labels, which would confuse the system about which partition to mount. Since I’m using the microSD card’s boot partition and the NVMe’s root partition, I needed to change identifiers to avoid conflicts.
Important: Before making any changes, note down the current UUIDs, PARTUUIDs, and labels of all partitions on both the drive as well as the microSD card:
$ sudo blkid /dev/mmcblk0p1 /dev/mmcblk0p2 /dev/sda1 /dev/sda2
Save this output somewhere safe. If something goes wrong, we’ll need these values to revert the changes.
Since the system will use the microSD card’s boot partition and the NVMe’s root partition, I needed to change identifiers on the unused partitions to avoid conflicts.
For the ext4 root partition on the microSD card (/dev/mmcblk0p2
):
I changed the microSD card’s root partition identifiers so it won’t conflict with the NVMe’s root partition:
# Change the label
$ sudo e2label /dev/mmcblk0p2 NIXOS_SD_UNUSED
# Change filesystem UUID
$ sudo tune2fs /dev/mmcblk0p2 -U random
# Verify changes
$ sudo blkid /dev/mmcblk0p2
This partition can be kept as a backup root filesystem in case the NVMe fails, or repurposed for other storage needs.
For the FAT32 boot partition on the NVMe (/dev/sda1
):
I also changed the NVMe’s boot partition identifiers so it won’t conflict with the microSD card’s boot partition:
# Change the label
$ sudo fatlabel /dev/sda1 FIRMWARE_UNUSED
# Change filesystem UUID using mlabel
$ echo "drive x: file=\"/dev/sda1\"" > /tmp/mtoolsrc
$ MTOOLS_SKIP_CHECK=1 sudo env MTOOLSRC=/tmp/mtoolsrc mlabel -N ABCDEF01 x:
# Verify changes
$ sudo blkid /dev/sda1
Note: The mlabel -N
command takes an 8-digit hexadecimal value for the FAT32 volume serial number.
We only change the filesystem UUID of the NVMe boot partition, not the disk signature, because changing
the disk signature would alter the PARTUUID of /dev/sda2
which needs to remain stable for cmdline.txt
to reference it correctly.
Step 4: Verify cmdline.txt points to the correct partition
The cmdline.txt
on the microSD card’s boot partition should reference the NVMe’s root partition.
Mine looks like this:
root=PARTUUID=2178694e-02 rootfstype=ext4 fsck.repair=yes rootwait rootdelay=30 console=tty1 console=serial0,115200n8 init=/sbin/init loglevel=7 lsm=landlock,yama,bpf
The root=PARTUUID=2178694e-02
should match the PARTUUID of the NVMe’s root partition (/dev/sda2
),
not the microSD card’s root partition.
The PARTUUID of NVMe root partition can be verified with:
$ sudo blkid /dev/sda2
The output will show both the filesystem UUID and partition PARTUUID. Make sure the PARTUUID in
cmdline.txt
matches what you see for /dev/sda2
.
Step 5: Boot with both drives connected
I connected both the microSD card and NVMe drive back into my RPi5 and powered it on. The system now:
- Reads the boot partition from the microSD card.
- Mounts the root filesystem from the NVMe drive.
- Runs NixOS entirely off the NVMe for all file operations.
Success! My NixOS root partition now runs off the NVMe drive, giving me the performance and reliability I was looking for.
Expanding the NVMe storage
After getting the hybrid boot working, I realized the NVMe drive had much more space than the microSD card I cloned from. The cloned root partition was only using the same amount of space as the original microSD card, leaving the rest of the NVMe drive unused.
The root partition on the NVMe can be resized to use all available space, or new partitions can be created for additional storage.