Problem: I was installing a Proxmox node remotely, in a co-location facility, with only IPMI for remote access (no keyboard or monitor). Therefore, a manual installation was not a viable option. I needed to boot from ZFS Mirror between two NVMe drives.
Constraints: My server is located 300 miles away from me. I must set up partitioning, UEFI boot, and create a fully functional hypervisor, without touching a physical console. If I make a typing mistake with regard to the disk paths, I will have to drive 3 hours round trip.
Solution: I have created a Debian preseed.cfg file that completely wipes both hard drives, creates the ESP and BIOS Boot partitions, creates a ZFS Rpool Mirror, installs the kernel and bootloader for Proxmox VE 8.2, and runs all of this with just one kernel command line. After a few painful reboots and a close call with a missing fallback boot entry, I now have an idempotent method for converting bare metal into a headless Proxmox Host in less than 10 minutes.
Quick Summary
- Install a Proxmox VE 8.2 netinstall ISO using a preseed URL via the GRUB command line.
- The preseed will partition 2 Identical Drives with the same layout (ESP, BIOS Boot, ZFS Data).
- The Installer will create a ZFS mirror Rpool and put the Root FS and Boot loader on the ZFS Mirror.
- When you boot the Hypervisor, it is fully headless, and you have UEFI Boot over SSH/IPMI.
Test Environment: We will use a Proxmox VE 8.2‑1 ISO (netinstall), Supermicro X11SSL‑F, 2 x 1TB Samsung 970 EVO Plus NVMe, April 2026. All commands and configurations were performed successfully on this combination.
Prerequisites & Planning
Before proceeding with the installation of the ISO to your server please ensure that you have the following three things completed first.
Gathering the Proxmox VE 8.2 Netinstall ISO
You can download the Proxmox VE 8.2 Netinstall ISO image from the official Proxmox download page and then you’ll need to verify that you have the correct SHA256 checksum (the original SHA256 checksum for this image can be found on our website). The Proxmox VE 8.2 Netinstall ISO is the same basic image that you’ll be using for manual installs, however, we will use automated preseed parameters to install Proxmox VE 8.2 on our server. Configure your server to boot to UEFI only; while the ISO can run in either legacy mode (BIOS) or in UEFI mode, the automated disk partitioning will expect that the server is using a GPT/EFI world.
Designing the ZFS Root Mirror Layout
You should make both disks identical to one another. I prefer to name both disks with /dev/disk/by-id/ names since they will remain consistent throughout PCIe enumeration order changes. For NVMe drives, this means nvme-Samsung_SSD_970_EVO_Plus_1TB_….
The partitioning I suggest will be:
- Partition #1: EFI System Partition (ESP), 512MiB, FAT32, Type=
EF00 - Partition #2: BIOS Boot Partition, 1MiB, Type=
EF02(required to boot GPT using GRUB) - Partition #3: ZFS Data, Rest of Disk, Type=
BF01
The Grub-EFI will embed itself to the ESP while it also uses the BIOS Boot Partition to maintain the GPT Boot chain. The ZFS Mirror Pool will be created on both drives within Partition #3.
Validating Hardware and Firmware
Connect to the UEFI Setup through the IPMI KVM, and disable the Compatibility Support Module (CSM).If the CSM gets enabled the installer creates a partial legacy configuration which doesn’t load properly when the system is set to run in pure UEFI configuration. Additionally, when debugging via Serial Console Redirection, ensure that the 115200 8n1 value is used. A quick review of the material located at Proxmox Wiki hardware compatibility list will provide you with lots of information; any UEFI compatible system manufactured within the last 10 years will work for the purposes of Proxmox running under your headless ZFS mirror automation.
Proxmox headless ZFS mirror automated partitioning preseed
The whole automation process is contained in a single Debian Preseed Configuration File. The final product requires no Manual partman Prompt Screens and it offers the Installer no type of Shell Prompt, it is all done unattended until it completes.
Crafting the Debian Preseed File for Unattended Disk Partitioning
If you plan on wiping out your current partitions; take the time to save your current Disk Layout first
It may be helpful to open a second IPMI SSH session useful so you can execute the following command:
lsblk -o NAME,SIZE,TYPE,MOUNTPOINT > /tmp/disk_layout_backup.txt
This is a Safety Net that has already helped me recover from a situation where I was testing a new Node with an errant Data Partition that I had forgotten about.
The structure of a Preseed file is defined by the partman-auto and partman-early commands/parameters that inform the Installer which target drives to use to create GPT Partitions. The following block is the Main Part of the Preseed and assumes that your two NVMe Drives are identified as nvme0n1 and nvme1n1 within the Installer. I generally always verify this by booting from a Live Image and executing the lsblk -f command before proceeding.
# Force GPT and wipe existing labels
d-i partman/early_command string \
debconf-set partman-auto/disk "$(list-devices disk | head -n1)"; \
for dev in $(list-devices disk); do \
dd if=/dev/zero of=$dev bs=1M count=16; \
sgdisk -Z $dev; \
done
# Partition recipe: 512MiB ESP, 1MiB BIOS boot, rest ZFS
d-i partman-auto/expert_recipe string \
zfs-mirror :: \
512 512 512 fat32 \
$primary{ } \
$bootable{ } \
method{ efi } format{ } . \
1 1 1 free \
method{ biosgrub } . \
1000 10000 1000000000 zfs \
$primary{ } \
method{ zfs } format{ } .
More information about all of the parameters can be found at the link Debian Installer Appendix B. The first dd+sgdisk -Z Combination deletes all previously established Partition Tables, allowing the Installer to see a clean drive when executing.
Next the Preseed will create a ZFS Rpool Mirror and the late_command command will call zpool create and zfs create and provide the necessary Mount Points prior to starting the Bootloader installation.The OpenZFS Debian Root on ZFS guide is clearly labelled as the structured hierarchy of datasets needed.
Injecting the Preseed into the Installer Boot
Booting from the Proxmox ISO, interrupt the GRUB menu (pressing e when at the Install Proxmox VE option) and add the item from the auto=true priority=critical url=http://your-internal-web/preseed.cfg list to the end of the line that starts with linux in the GRUB menu.
If you are booting using virtual media through IPMI, simply load a FAT32-formatted USB device containing the preseed file and place it in the file=/hd-media/preseed.cfg location. Once you press Ctrl+X you will not be asked any further questions by the installer.
Enabling UEFI Boot and ZFS Boot Environment
By using a preseed, I was able to tell GRUB where to correctly install the EFI payload. The stock Proxmox installer usually makes this determination for you, but using a preseed you must state this explicitly as follows:
in-target grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=proxmox --no-nvram --removable
in-target update-grub
The --removable specifies a default bootx64.efi switch to /EFI/boot which was the only thing that caused me to avoid the disaster of a “BootDevice Not Found” error (more on this to come).
On the first reboot, the system will now boot from the rpool dataset defined in the rpool/ROOT entry, and the rpool entry will be used as the bootsource location.
Verifying the ZFS Mirror and Boot Chain
Once the node has rebooted for the first time, SSH in via IPMI and issue the following commands to verify that everything is working correctly.
Verify ZFS mirroring status:
root@pve-headless:~# zpool status -v rpool
pool: rpool
state: ONLINE
config:
NAME STATE READ WRITE CKSUM
rpool ONLINE 0 0 0
mirror-0 ONLINE 0 0 0
ata-Samsung_SSD_970_EVO_Plus_1TB_S6S2NS0X123456 ONLINE 0 0 0
ata-Samsung_SSD_970_EVO_Plus_1TB_S6S2NS0X789012 ONLINE 0 0 0
Both disks report ONLINE, therefore the mirror is intact.
Verify UEFI boot entry:
root@pve-headless:~# efibootmgr -v
BootCurrent: 0001
BootOrder: 0001,0000
Boot0001* proxmox HD(1,GPT,1a2b3c4d-1234-5678-9abc-def012345678,0x800,0x100000)/File(\EFI\proxmox\grubx64.efi)
The above line confirms to the firmware where the bootloader can be located. No guessing, no manual intervention is necessary.
Optimization and Best Practices
Post‑Install Tuning for ZFS Root Mirror
The Proxmox installer automatically sets up the ashift=12 on the NVMe drive, which is correct. I then layer on a few dataset properties to make the hypervisor hum.
First, on the pool itself:
zfs set compression=lz4 rpool
zfs set atime=off rpool
That turns on lightweight LZ4 compression and disables access-time writes across all datasets in the pool. Next, I set relatime=on on the rpool/ROOT dataset so container and VM image datasets don’t constantly write access time updates to disk. Finally, autotrim=on on the pool lets the SSDs handle their own garbage collection, saving you from having to schedule manual zpool trim cron jobs.
After applying those, check that everything stuck:
zfs get compression,atime,relatime,autotrim rpool/ROOT
Enforcing Idempotent Re‑deployment
Since the dd+sgdisk -Z portion of the preseed file overwrites any existing ZFS labels for the target system, you can safely rerun the preseed file multiple times without worrying about losing data. This feature enables developers to precisely replicate a server without needing to interact with the console, facilitating CI (Continuous Integration) based bare metal testing.
Common Pitfalls and Edge Cases
What Didn’t Work For Me
The first three times I installed Proxmox resulted in a black screen stating “BootDevice Not Found.” The pool was healthy and the ESP contained the grubx64.efi binary. However, the UEFI firmware could not locate it. After starting a live USB and using the chroot command to access the system, I executed the command:
efibootmgr -v
to verify the presence of the proxmox entry. Unfortunately, none existed. The reason was that my preseed file invoked the grub-install without specifying the --removable flag. As a result, the firmware’s non-volatile random access memory (NVRAM) was not written over IPMI (Intelligent Platform Management Interface) and therefore lacked a boot option.I added --removable to grub-install in late_command, so that bootx64.efi was placed in the fallback path. After doing this, I was able to boot the firmware blind, by falling back to the \EFI\boot\bootx64.efi.
This one flag saved me a long drive. I’ve added it to every preseed I’ve written since.
Edge Case: Hybrid UEFI/Legacy Boot Conflicts
If a board has CSM enabled, at times the installer will write an MBR boot sector along with the ESP. This creates a legacy boot chain, but a UEFI panic will occur. To avoid problems in the future, make sure your board is set to UEFI-only prior to booting the ISO. In cases where you don’t have access to the firmware, you can add force-efi-extra-removable to the kernel command line to steer the installer down the pure EFI path.
When Disk‑by‑ID Paths Drift
After a reboot, NVMe drives on certain platforms can show up as different /dev/disk/by-id/ names, particularly if you are utilizing USB to NVMe or U.2 hot plug backplanes. The Arch Wiki persistent block device naming page denotes the hierarchy. If maximum stability is desired, I have switched to /dev/disk/by-id/wwn-* or created a custom udev rule to create symlinks based on PCIe slots. When going this route, please remember to change the preseed’s partman-auto/disk detection and zpool create commands.
Frequently Asked Questions
How can I verify the ZFS mirror is functioning and bootable without having a monitor connected?
You can SSH into the server (or use IPMI Serial‑over‑LAN) and run zpool status -v rpool and efibootmgr -v. If you see a green light indicating both devices are functioning with an entry pointing to \EFI\proxmox\grubx64.efi, this indicates a successful headless build and no monitor is needed.
Can I add a third disk to the ZFS mirror later on?
Yes. You can zpool attach rpool <existing-disk> <new-disk-id> after installation; however, you will need to manually install the bootloader on the ESP of the new disk and adjust the UEFI boot order. This will be part of the expansion task after the initial preseed.
If my server does not have IPMI and the automation install is hanging, how do I troubleshoot without seeing anything?
You could make a debug preseed that adds console=ttyS0,115200 to the kernel command line, and then connect a null modem cable between your server and a laptop. If that is not an option, you can use the installer netconsole feature (netconsole=@/eth0,@192.168.1.100/) to redirect kernel output to a syslog.