This series is also available in French.
Lire en Français
Chapter 2
Home LabTutorialsDevOps & Infrastructure

How to move the Raspberry Pi 5 OS to an NVMe SSD

6 min read
How to move the Raspberry Pi 5 OS to an NVMe SSD
Learn how to manually migrate your Raspberry Pi 5 operating system from a USB stick or SD card to a faster NVMe SSD. This step-by-step guide covers partitioning, copying files, updating boot configurations, and avoiding common pitfalls.

So far I have a working pi running with its OS on a USB stick. In your case it might be on a SD card. I moved the OS to the SSD because it's faster and more durable. Additionally, since I don't use SD cards, it was my only storage option. However, the Raspberry Pi Imager doesn’t let you choose to do that and I ended up having to copy the OS to the SSD manually. Let me explain how to do it and the pitfalls I ran into.

The migration has four parts:

  1. partition and format the NVMe
  2. copy the running OS from USB to the NVMe
  3. update boot configuration so Linux mounts the NVMe root filesystem
  4. change the EEPROM boot order so the Pi prefers NVMe

Step 1: A few checks

First, ensure your Pi 5 has the latest firmware, which includes the most recent NVMe boot support.

  1. Open an ssh terminal and run:
sudo apt update && sudo apt full-upgrade -y
  1. Update your Pi's EEPROM (bootloader):
sudo rpi-eeprom-update -a
  1. Reboot your Pi:
sudo reboot

Then we need to check what the system sees, run this:

lsblk -o NAME,SIZE,FSTYPE,MOUNTPOINT
sudo blkid

In a typical setup:

  • the USB system disk shows up as sda
  • the NVMe shows up as nvme0n1

Then we need to make sure we have properly set up the pcie slot on the Pi. Run this command:

sudo nano /boot/firmware/config.txt

And add these lines after [all] if they are not present.

[all]
dtparam=pciex1
dtparam=pciex1_gen=3

Step 2: Partition and format the NVMe

First, remove the old partition layout that comes with the SSD and initialize the disk with GPT (GUID Partition Table, not the AI model!):

sudo parted -s /dev/nvme0n1 mklabel gpt

Then, create a boot partition of about 512 MiB:

sudo parted -s /dev/nvme0n1 mkpart primary fat32 1MiB 513MiB

Why start at 1MiB instead of 0? It keeps the partition aligned properly. Proper alignment improves compatibility and performance.

We also need to set a boot-related EFI System Partition flag so the BIOS understands it as a boot partition:

sudo parted -s /dev/nvme0n1 set 1 esp on

Next, create the main Linux partition that will hold the root filesystem:

sudo parted -s /dev/nvme0n1 mkpart primary ext4 513MiB 100%

Then, format partition 1 as FAT32 for boot files and partition 2 as ext4 for the main Linux system:

sudo mkfs.vfat -F 32 /dev/nvme0n1p1
sudo mkfs.ext4 -F /dev/nvme0n1p2

That layout mirrors the normal Raspberry Pi OS pattern: a boot partition plus a separate root filesystem, with the root later referenced by PARTUUID. As for the different file system, the Raspberry Pi firmware usually cannot boot directly from an ext4 boot partition so FAT32 is a safe bet. However, you cannot use FAT32 for Linux since it lacks Unix permissions and other core filesystem features Linux expects.

Step 3: Copy the running OS from USB to the NVMe

First, create temporary mount points and mount the SSD partitions there. Once mounted, these paths expose the contents of the NVMe partitions, which makes it easy to copy the system onto them.

Create folders:

sudo mkdir -p /mnt/nvmeroot
sudo mkdir -p /mnt/nvmeroot/boot/firmware

Mount the partitions on them:

sudo mount /dev/nvme0n1p2 /mnt/nvmeroot
sudo mount /dev/nvme0n1p1 /mnt/nvmeroot/boot/firmware

Then we copy the OS using rsync with /mnt/nvmeroot as a target so you preserve permissions, ownership, ACLs, xattrs, and symlinks while avoiding pseudo-filesystems such as /proc and /sys.

sudo rsync -aAXHv --numeric-ids --info=progress2 \
 --exclude=/dev/* \
 --exclude=/proc/* \
 --exclude=/sys/* \
 --exclude=/tmp/* \
 --exclude=/run/* \
 --exclude=/mnt/* \
 --exclude=/media/* \
 --exclude=/lost+found \
 / /mnt/nvmeroot

Step 4: Read the new PARTUUIDs

Now get the SSD partition IDs:

sudo blkid /dev/nvme0n1p1 /dev/nvme0n1p2

You need two values:

  • nvme0n1p1 → boot partition PARTUUID
  • nvme0n1p2 → root partition PARTUUID

These exact values must be used consistently in the next step.

Step 5: Update cmdline.txt

This is a boot parameter file read very early during startup.

Open the file:

sudo nano /mnt/nvmeroot/boot/firmware/cmdline.txt

Find the existing:

root=PARTUUID=...

Replace it with the PARTUUID of nvme0n1p2.

Important:

  • cmdline.txt must remain a single line
  • the root=PARTUUID must point to partition 2 (nvme0n1p2), not the boot partition

Raspberry Pi OS uses PARTUUID here instead of a device name like /dev/nvme0n1p2 or /dev/sda2, because device names can change between boots. If this value is wrong, the Pi may find the boot files but fail to mount the root filesystem.


Step 6: Update /etc/fstab

It tells Linux which filesystems should be mounted automatically after boot, and where.

Open:

sudo nano /mnt/nvmeroot/etc/fstab

We need to edit it to make sure it references the NVMe partitions, not the old USB ones.

Typical result:

proc            /proc           proc    defaults          0       0
PARTUUID=<boot-partuuid>  /boot/firmware  vfat    defaults          0       2
PARTUUID=<root-partuuid>  /               ext4    defaults,noatime  0       1

Replace:

  • <boot-partuuid> with the PARTUUID of nvme0n1p1
  • <root-partuuid> with the PARTUUID of nvme0n1p2

So you should have something like:

A bad /etc/fstab can leave the system unable to finish booting cleanly, and Raspberry Pi forum guidance explicitly warns that a wrong PARTUUID there can drop the machine into emergency mode.


Step 7: Unmount and switch boot order

You unmount the SSD so Linux finishes and closes the filesystem properly before trying to boot from it.

First, force pending filesystem writes from memory to be flushed to disk using the command:

sync

Then, we unmount:

sudo umount /mnt/nvmeroot/boot/firmware
sudo umount /mnt/nvmeroot

Finally we make sure to boot from the nvme SSD and reboot:

sudo raspi-config nonint do_boot_order B2
sudo reboot

Raspberry Pi documents B2 as NVMe/USB Boot, meaning the bootloader tries NVMe first, then USB, then SD card.


Step 8: Verify that you are really running from the NVMe

After reboot, do not remove the USB drive yet. First verify what actually mounted:

findmnt /
findmnt /boot/firmware
cat /proc/cmdline
lsblk -o NAME,SIZE,FSTYPE,MOUNTPOINT

You want to see:

  • / mounted from nvme0n1p2
  • /boot/firmware mounted from nvme0n1p1
  • cat /proc/cmdline showing the same root PARTUUID you put into the SSD’s cmdline.txt

Only after that check should you power down and remove the USB boot drive.


Step 9: Expand the root filesystem if needed

Once you know the Pi is really running from NVMe, run:

df -h /

If the root filesystem already shows the expected SSD size, you can skip this step.

Otherwise, expand the root partition to use the full SSD:

sudo raspi-config nonint do_expand_rootfs
sudo reboot

Raspberry Pi documents do_expand_rootfs as the noninteractive way to grow the root filesystem after cloning or resizing.

The pitfalls I ran into

Although everything we have done can be executed via ssh, if you mess some things up, you might need a direct connection so I would suggest grabbing an HDMI cable with a male micro-HDMI for debugging.

This was my case and here are the few issues I ran into.

Pitfall 1: Using the wrong PARTUUID

Double check whether the exact root PARTUUID in cmdline.txt matches nvme0n1p2 meaning the root partition.

Pitfall 2: Editing the wrong file

When you are booted from USB and preparing the SSD, you are editing the future SSD copy, not the currently running system.

That means you have to edit /mnt/nvmeroot/boot/firmware/cmdline.txt and /mnt/nvmeroot/etc/fstab. Not the live system’s /boot/firmware/cmdline.txt and /etc/fstab that exists only on the USB stick.

Pitfall 3: cmdline.txt must stay on one line

cmdline.txt is not a normal config file with one option per line. It is a single-line kernel command line. If you insert line breaks or split it carelessly, boot can fail.

Pitfall 4: The Pi kept booting the broken NVMe instead of falling back to USB

If the EEPROM boot order is set to NVMe first, and the NVMe is “bootable enough” to get into the early stages, the Pi may continue trying the SSD rather than falling back neatly to USB.

A half-broken NVMe can be worse than a fully absent one, because it can still win the boot race. Sometimes the fastest recovery is to wipe the SSD’s partition table, since we are only copying the OS that is not too bad.

If the Pi keeps preferring a broken NVMe and you cannot easily remove the HAT or SSD physically, one blunt but effective recovery path is to destroy the partition table on the NVMe so it is no longer bootable. Once the SSD no longer looks bootable, the bootloader can move on to USB according to boot order.

A typical wipe looks like this:

dd if=/dev/zero of=/dev/nvme0n1 bs=1M count=100

That writes about 100 MB of zeroes to the start of the SSD and usually destroys the partition table and early boot metadata.

Pitfall 5: Verifying after reboot matters more than the copy itself

You can do the copy perfectly and still remain booted from the USB drive after reboot if the boot order, PARTUUIDs, or boot files are not right.

Always check before removing the USB drive:

findmnt /
findmnt /boot/firmware
cat /proc/cmdline

Final thoughts

Moving Raspberry Pi OS from a USB boot drive to an NVMe SSD is not hard, but it can be tricky.

The copy itself is the easy part. The real difficulty is that boot depends on several layers agreeing with each other:

  • the EEPROM boot order
  • the boot partition contents
  • the kernel command line
  • the root filesystem identifiers in /etc/fstab

The good news is that once you understand the possible failure modes, it is easy to fix. Check the devices, check the PARTUUIDs, verify after reboot, and do not remove the USB drive until you have proven the system is actually running from NVMe.

That’s it! The Pi hardware and OS are now ready for anything we want. In the next chapter, we will see how to install Pi-hole as a local DNS server for ad-blocking and more.

#raspberry-pi#linux#nvme#homelab
Judicael Poumay (Ph.D.)

Judicael Poumay (Ph.D.)

Follow me on LinkedIn for weekly content Judicaël Poumay

As an independent AI researcher/developer specialized in Natural Language Processing (NLP), I have a comprehensive expertise in the development and integration of AI systems, as well as data analysis.

Is your company looking to integrate AI solutions, analyze data, or strengthen its back-end development? Contact me!

Buy me a beer 🍺

Related Articles