These are my notes on setting up two Raspberry Pi 5s (Tempus and Ancilla) with NVMe SSDs and PoE+ HATs, booting from NVMe rather than SD card. The HAT in use is the UCTRONICS U6287 PoE M.2 HAT, which provides PoE+ power, an M.2 NVMe slot, and a heatsink/fan — all in one board. It connects via the Pi 5’s PCIe FFC connector and passes through 8 GPIO pins.
Hardware
- Raspberry Pi 5
- UCTRONICS U6287 PoE M.2 HAT (NVMe + PoE+)
- Kingston 128GB M.2 NVMe SSD (2242 form factor)
- SD card (temporary, for initial setup only)
Flash the SD card
Use Raspberry Pi Imager on any PC/Mac. Choose Raspberry Pi OS Lite (64-bit) — it’s under Raspberry Pi OS (other), not the top-level list. Before writing, open the customisation options and set:
- Hostname (e.g.
tempus) - Username and password
- Enable SSH
- Locale and keyboard layout
Write to the SD card. Note: the customisation settings carry over to the SD card image, but not to anything subsequently written to NVMe — you’ll need to reconfigure after moving to NVMe.
Boot from SD card and prepare the NVMe
Insert the SD card, attach the NVMe HAT with the SSD installed, and power on. SSH in once it’s up. First, install nvme-cli and do a sanity erase of the new drive:
sudo apt install nvme-cli
sudo nvme sanitize /dev/nvme0n1 --sanact=2
sudo nvme sanitize-log /dev/nvme0n1
Check the sanitize log output — SSTAT: 0x2 and SPROG: 32767 (0x7FFF) means complete and successful.
Write the OS to the NVMe
Download the current Raspberry Pi OS Lite image and write it directly to the NVMe:
wget https://downloads.raspberrypi.com/raspios_lite_arm64/images/raspios_lite_arm64-2024-11-19/2024-11-19-raspios-bookworm-arm64-lite.img.xz
xz -dc 2024-11-19-raspios-bookworm-arm64-lite.img.xz | sudo dd of=/dev/nvme0n1 bs=4M status=progress
Check the current image filename at raspberrypi.com/software/operating-systems/ as it changes with each release.
Set NVMe as boot device
sudo raspi-config
Go to Advanced Options → Boot Order → NVMe/USB Boot. Then shut down, remove the SD card, and reboot — the Pi should come up from NVMe.
sudo shutdown -h now
First boot from NVMe — reconfigure
Since the NVMe has a fresh image, SSH won’t be enabled and the hostname will be default. Hook up a monitor and keyboard for the first boot, then run:
sudo raspi-config
Set:
- System Options → Hostname
- Interface Options → SSH → Enable
- Localisation Options → Locale — set
en_US.UTF-8and make it system default, then reboot before setting keyboard or you’ll get locale errors - Localisation Options → Keyboard — after locale is set and rebooted
Disable WiFi and Bluetooth
These Pis are wired-only, so remove the wireless stack and disable the hardware:
sudo apt purge wpasupplicant wireless-tools rfkill
sudo apt autoremove
Add to /boot/firmware/config.txt:
dtoverlay=disable-wifi
dtoverlay=disable-bt
Stop NetworkManager from overwriting resolv.conf
NetworkManager will keep overwriting /etc/resolv.conf by default. To stop it:
sudo nano /etc/NetworkManager/NetworkManager.conf
Add under [main]:
dns=none
sudo systemctl restart NetworkManager
Now /etc/resolv.conf can be edited directly and will stay put.
Gotcha: cmdline.txt PARTUUID is unique per device
When setting up the second Pi (Ancilla), I made the mistake of copying cmdline.txt from Tempus. This overwrote Ancilla’s root partition PARTUUID with Tempus’s, causing a boot failure and drop to initramfs shell with:
ALERT! PARTUUID=2bcbfb2c-02 does not exist. Dropping to a shell!
The fix from the initramfs shell:
mount /dev/nvme0n1p2 /root
mount /dev/nvme0n1p1 /root/boot/firmware
mount --bind /dev /root/dev
mount --bind /proc /root/proc
mount --bind /sys /root/sys
chroot /root
update-initramfs -u
And edit /boot/firmware/cmdline.txt to put the correct root=/dev/nvme0n1p2 or the correct PARTUUID back. Never copy cmdline.txt between machines — everything else is fair game.