Correction (2026-08-19): the original version of this post said rpi-clone
simply doesn't touch cmdline.txt. That was wrong, and the real mechanism is
more interesting. framps pointed out on the upstream
issue
that billw2/rpi-clone has been
unmaintained since March 2024 and that
geerlingguy/rpi-clone is the
maintained fork, where this is already fixed. Both claims check out. The post
below has been corrected: if you use the maintained fork, you will not hit
this at all. It's still worth reading if you're on the old version, or if you
want the diagnosis technique — the failure mode is a good example of a check
that silently passes against the wrong file.
TL;DR: On Bookworm and later, cmdline.txt moved from /boot/ to
/boot/firmware/. The unmaintained billw2/rpi-clone has code to rewrite the
destination's root=PARTUUID=..., but it hardcodes the old path — where the
OS now leaves a placeholder file that contains no PARTUUID at all. So the fix-up
runs, matches nothing, and reports success. Flip the boot order to USB before
catching this, and your Pi either boots the wrong disk or fails to boot at all.
Use geerlingguy/rpi-clone and this
doesn't happen.
The setup
Moving a Raspberry Pi's root filesystem off the SD card and onto a USB-attached SSD is one of the highest-value changes you can make to a Pi that's meant to run unattended for months: SD cards wear out under sustained write load, SSDs don't (practically speaking), and boot/IO latency drops noticeably as a bonus.
The standard playbook is:
- Update the bootloader EEPROM (
sudo rpi-eeprom-update -a), reboot. - Clone the running SD card to the SSD with
rpi-clone. - Flip
raspi-config→ Advanced Options → Boot Order to prefer USB. - Reboot and confirm
findmnt /shows the SSD.
Steps 1, 3, and 4 are exactly as advertised. Step 2 has a landmine in it — but only if you're running the version most search results still point you at.
What actually happens
rpi-clone does a genuinely good job of the bulk of the work: it partitions the destination to match the source, rsyncs the filesystem across, and rewrites the destination's /etc/fstab with the destination's own PARTUUIDs. If you check fstab after cloning, it's correct.
It also has a section that is supposed to do the same for cmdline.txt. In the unmaintained version, it looks like this:
# Fix PARTUUID or device name references in cmdline.txt and fstab
cmdline_txt=${clone}/boot/cmdline.txt
That path is the problem. Starting with Bookworm, the boot partition is mounted at /boot/firmware, not /boot, and cmdline.txt went with it. What's left behind at the old location is a placeholder the OS ships deliberately:
$ cat /boot/cmdline.txt
DO NOT EDIT THIS FILE
The file you are looking for has moved to /boot/firmware/cmdline.txt
So the fix-up code does run. It opens that file, greps it for the source disk's PARTUUID, finds nothing (there are no PARTUUIDs in it — it's three lines of English), and moves on without editing anything and without warning you. The real cmdline.txt, sitting in /boot/firmware/ on the destination, keeps the source disk's root=PARTUUID=..., copied verbatim by the rsync.
This is worse than not handling cmdline.txt at all, because the code looks like it handles it. Reading the script doesn't reveal the bug; you have to know that the path moved.
The maintained fork fixes exactly this by discovering the path at runtime instead of hardcoding it:
# Starting with bookworm /boot/firmware/cmdline.txt is used.
cmdlinedir=$(mount | awk '$3 ~ "^/boot(/firmware)?$" && $5 == "vfat" { print substr($3, 2); exit }')
Why it bites you specifically at the worst moment
You won't notice during the clone. You won't notice right after, either — you're still running from the SD card at that point, so everything looks fine. The mismatch only matters the moment the firmware actually tries to boot the kernel from the SSD, using the SSD's own cmdline.txt — which is exactly what happens right after you flip the boot order and reboot.
At that point the kernel is told "find your root filesystem at PARTUUID 10052bbd-02" (the SD card's identifier), while the disk it's actually booting from carries an entirely different, freshly-generated PARTUUID (say, b11c96d4-02). Depending on whether the SD card is still physically present, you get either a boot that silently falls back to the SD card's root filesystem (confusing — your changes "don't stick"), or a kernel panic waiting for a root device that isn't there (worse).
How to catch it before rebooting
Right after the clone finishes — before you touch the boot order — compare the source and destination PARTUUIDs directly. Note the destination path: /boot/firmware/cmdline.txt lives on the boot partition, so mounting /dev/sda1 gives you cmdline.txt at the mount root.
# What the SD card's root partition is actually called
sudo blkid /dev/mmcblk0p2
# What the SSD's root partition is actually called
sudo blkid /dev/sda2
# What the SSD's own boot config *thinks* the root partition is called
sudo mount /dev/sda1 /mnt
grep -o 'root=PARTUUID=[a-f0-9-]*' /mnt/cmdline.txt
If the third value matches the first (the SD card's) instead of the second (the SSD's own), you have the bug.
The fix
sudo sed -i "s/root=PARTUUID=<OLD_PARTUUID>/root=PARTUUID=<NEW_PARTUUID>/" /mnt/cmdline.txt
Then do one more sweep to make sure nothing else on the destination still references the stale identifier — it's a one-liner and costs nothing:
sudo grep -rl "<OLD_PARTUUID>" /mnt 2>/dev/null
If that returns nothing, unmount and reboot into the boot-order change with confidence.
The better fix, if you haven't cloned yet: use geerlingguy/rpi-clone, which handles this correctly. framps also maintains syncUUIDs, which checks and optionally repairs UUID mismatches in both fstab and the kernel command line after the fact.
A ready-to-run version of this check-and-fix — with a dry-run mode and an automatic backup of cmdline.txt before it touches anything — is in the toolkit that comes with this post.
The general lesson
The instinct after a failure like this is "the tool didn't do X." Often the truth is "the tool tried to do X somewhere that stopped existing," which is a different bug with a different fix — and one you'll misdiagnose if you stop at the symptom.
What made this one invisible is that the failed step had no failure. The code path ran, its search matched zero lines, and zero matches is indistinguishable from "nothing needed changing." A check that can't tell absent from already fine will report success in both cases, and you find out which one it was at boot time.
Two habits fall out of that. After any disk clone, before changing what your firmware boots from, grep the new disk for the old disk's identifiers — verify the outcome, not the exit code. And when a tool with thousands of stars behaves oddly on a current OS, check when it was last touched before assuming your setup is at fault: search rankings reflect accumulated popularity, not current maintenance.
Toolkit
This post's fix is available as a tested, ready-to-run script in the toolkit.








