root=UUID= Worked in Every Guide I've Read, and Still Panicked This Kernel

Building a Linux system from scratch means writing your own GRUB entry, which means picking how the kernel finds its root filesystem. The obvious choice, root=UUID=<filesystem-uuid>, is what nearly every guide on the internet uses. It's also exactly wrong for a kernel with no initramfs, and the panic it produces looks like a completely different bug.

The panic that looked like a race condition

The symptom was a boot-time kernel panic: VFS: Cannot open root device ... or unknown-block(0,0). The obvious suspect was timing — this kernel's NVMe driver probes its controller asynchronously, and by default the kernel only waits a few seconds for the root device to show up before giving up. That's a real bug, and the real fix for it is the rootwait kernel parameter, which tells the kernel to wait for asynchronous device probing to finish instead of racing it. Adding rootwait was correct. It didn't fix the panic.

The second bug hiding under the same symptom

The actual cause was a different, unrelated property of this specific kernel: it boots with no initramfs, which means there's no udev/blkid running early to resolve device identifiers before the kernel needs its root filesystem. Without that, the kernel falls back to its own early root-lookup code — in current kernels, this lives in block/early-lookup.c, called from init/do_mounts.c — and that code only understands a specific set of prefixes: PARTUUID=, PARTLABEL=, a literal device path like /dev/nvme0n1p5, or a raw <major>:<minor> pair. The older behavior — resolving a plain UUID= by scanning every block device's filesystem superblock for a match — belongs to a different code path, the one an initramfs's own udev normally handles before the kernel ever sees root= at all. A no-initramfs kernel parsing root= directly never gets that path.

root=UUID=<filesystem-uuid> falls through every prefix the early-lookup code actually recognizes. It doesn't error out cleanly — the parser tries to interpret the leftover string as a raw major:minor device number, fails, and early_lookup_bdev() returns -EINVAL. The kernel reports this as "Disabling rootwait; root= is invalid," immediately followed by the same VFS panic as before. The UUID itself was correct the entire time. It was never a valid way to specify it here.

Why this reads as one bug instead of two

The kernel's own panic screen prints an "available partitions" dump when root-mounting fails, and that dump shows both identifiers for each partition — the partition's own PARTUUID and the filesystem's UUID — sitting right next to each other. Adding rootwait without also switching identifiers looks, from the panic message alone, exactly like a race condition that wasn't fully fixed: same panic text, same "wait longer" instinct, and a plausible fix (rootwait) that genuinely does something real just not the thing actually needed. Nothing about the error message distinguishes "the device isn't ready yet" from "the identifier format the kernel is looking at doesn't parse."

What actually fixed it

The GRUB entry changed on two axes at once: rootwait was added (real, needed, addresses genuine NVMe probe timing), and root=UUID=5889e0d4-0d2a-46fe-a38e-d9fe94066aa1 became root=PARTUUID=60740bbc-da47-4320-bc60-3abd0d9a52c9 — the GPT partition's own identifier, pulled via blkid -s PARTUUID, not the ext4 filesystem's identifier. Both changes were real and both were necessary, but only one of them actually explains why the original panic happened. Building a kernel with no initramfs means giving up the convenience of a full udev-backed root lookup, and that convenience is exactly what makes UUID= look interchangeable with PARTUUID= on every other Linux system most people have ever configured. On this one, they're not the same option with two spellings — one of them the early-lookup code has literally never heard of.

Add new comment

Restricted HTML

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • Lines and paragraphs break automatically.
  • Web page addresses and email addresses turn into links automatically.
Please share this article on your favorite website or platform.