Install Proxmox
Hardware
Step 1) Pick Hardware. Important is CPU support. Mostly ARM or x86_64.
– My host a HPE DL160 G9.
Software
Step 2) Install using appropriate installer image.
– In my case x86_64 version 9.2
– I installed to an internal 32GB sd card.
Storage (Physical)
Step 3) VM Storage.
I’ve discussed this in the past specially when it comes to shared storage options. There you can see a picture of all the options available to Proxmox, and if it supports snapshots or if its shared. For ease sake of this post we’re going to stick to local storage. With the minted information from that chart alone ZFS would seem the winner, however….
Choosing the right storage architecture for a virtualization host requires a careful balance between resource allocation, hardware capabilities, and performance goals. For this build—featuring 50 GB of memory, an HPE B140i controller running in SATA AHCI pass-through mode, and a mix of SSDs and a mechanical drive—maximizing raw performance and preserving system RAM for virtual machines is our primary objective. By selecting LVM-Thin instead of ZFS, we bypass the heavy computational and memory overhead of a Copy-on-Write filesystem, ensuring that nearly all 50 GB of RAM remains dedicated strictly to our workloads. The design stripes multiple solid-state drives into a single, high-performance LVM-Thin volume group to multiply IOPS and throughput for VM boot disks. Meanwhile, the standalone 4TB spindle drive is formatted as a standard, zero-RAM-footprint Linux directory to act as an isolated target for possible Proxmox backups and static file shares. This hybrid, LVM-centric approach eliminates storage controller bottlenecks, maximizes the lifespan and speed of our SSDs, and relies on a robust backup strategy rather than restrictive hardware or software redundancy.
After messing around about an hour, I found out the reason I wasn’t seeing the drives was due to a controller configuration (it was already set to Sata AHCI support mode), which I double verified by seeing the disks and running dd commands against them to get sequential performance numbers. The reason, was cause apparently in this mode drives are not hot swapable.
<rant> Stupid ass HP, always causing me to waste my life away cause of their stupid ass storage controller and firmware/driver choices.. ughhh </rant>
Turn off Swap
SIDE QUEST! Congratulations you just entered a side quest on your way to configuring your storage for your PVE hypervisor. SWAP!
An critical optimization step for any Proxmox host booting from flash media is managing the system’s swap space. By default, the Debian-based Proxmox installer creates a virtual memory swap partition directly on the boot drive. When running Proxmox from an internal SD card, leaving swap enabled is a hardware hazard; Linux will continuously shift idle processes onto the card, exhausting its low write-endurance and risking boot environment corruption. Because this host boasts a healthy 50 GB of physical RAM, we immediately disabled and removed the default swap volume to shield the SD card from unnecessary wear. However, completely lacking a swap space can lead to kernel instability under unexpected memory spikes. Our strategy resolves this by re-establishing a dedicated swap space directly on our new solid-state storage tier. Crucially, this swap will not be placed inside the dynamic LVM-Thin pool—which can cause file system deadlocks and severe latency—but will instead be carved out as a fixed, pre-allocated ‘Thick’ LVM volume. This hybrid approach ensures the SD card remains read-heavy and protected, while giving the host an ultra-fast, safe SSD safety net without sacrificing valuable system memory to ZFS.
1. Turn off active swap immediately
swapoff -v /dev/mapper/pve-swap
2. Stop it from turning back on when you reboot
nano /etc/fstab
pve-swap. It will look similar to this:/dev/pve/swap none swap sw 0 0
# at the very beginning of that line to comment it out and disable it permanently:# /dev/pve/swap none swap sw 0 0
3. Delete the volume entirely (Optional but recommended)
lvremove /dev/pve/swap
Back to Storage (Logical)
Step 1: Create the Physical Volumes (PV)
pvcreate /dev/sda /dev/sdb /dev/sdc
Physical volume "/dev/sda" successfully created. x3Step 2: Combine them into a Volume Group (VG)
pve-fast.vgcreate pve-fast /dev/sda /dev/sdb /dev/sdc
Volume group "pve-fast" successfully created.Step 3: Verify the Master Pool
vgs pve-fast
pve-fast listed with 3 physical volumes (#PV) and a total size that roughly equals the combined capacity of your three SSDs.root@g9-pve:~# pvcreate /dev/sda /dev/sdb /dev/sdc Physical volume "/dev/sda" successfully created. Physical volume "/dev/sdb" successfully created. Physical volume "/dev/sdc" successfully created. root@g9-pve:~# vgcreate pve-fast /dev/sda /dev/sdb /dev/sdc Volume group "pve-fast" successfully created root@g9-pve:~# vgs pve-fast VG #PV #LV #SN Attr VSize VFree pve-fast 3 0 0 wz--n- <670.70g <670.70g
Creating the Volume Group (VG) only defines the boundaries of your master pool. It tells LVM: “You are allowed to use the storage blocks inside sda, sdb, and sdc.” It does not decide how data is laid out yet.
The master pool itself is neutral. The choice between Linear or Striped happens entirely in the next step when we create the Logical Volumes (LVs) inside that pool. Why it’s like this, I dunno, I’m just here to figure out how it works.
Back to Swap
-i 3 flag to guarantee that any memory swapped to disk is interleaved across all three SSD controllers simultaneously for maximum throughput.1. Create the Striped Logical Volume
fast-swap from your pve-fast volume group.lvcreate -L 4G -i 3 -I 64k -n fast-swap pve-fast
-L 4G: Allocates exactly 4 Gigabytes of space.-i 3: Forces the volume to stripe data across exactly 3 physical disks (RAID0 behavior).-I 64k: Sets the optimal block stripe size for performance.
2. Format the Volume for Swap
mkswap /dev/pve-fast/fast-swap
3. Activate the New Swap Space
swapon /dev/pve-fast/fast-swap
4. Make it Permanent Across Reboots
echo '/dev/pve-fast/fast-swap none swap sw 0 0' >> /etc/fstab
Verify the Configuration
swapon --show
/dev/mapper/pve--fast-fast--swap listed as the sole active device, confirming your SD card is safe.root@g9-pve:~# lvcreate -L 4G -i 3 -I 64k -n fast-swap pve-fast Rounding size 4.00 GiB (1024 extents) up to stripe boundary size <4.01 GiB (1026 extents). Logical volume "fast-swap" created. root@g9-pve:~# mkswap /dev/pve-fast/fast-swap Setting up swapspace version 1, size = 4 GiB (4303351808 bytes) no label, UUID=a6ec7580-dc9c-40de-8405-b9f8c66a41fd root@g9-pve:~# swapon /dev/pve-fast/fast-swap root@g9-pve:~# echo '/dev/pve-fast/fast-swap none swap sw 0 0' >> /etc/fstab root@g9-pve:~# swapon --show NAME TYPE SIZE USED PRIO /dev/dm-0 partition 4G 0B -1 root@g9-pve:~# fdisk -l | grep mapper Disk /dev/mapper/pve-root: 12.8 GiB, 13740539904 bytes, 26836992 sectors Disk /dev/mapper/pve--fast-fast--swap: 4.01 GiB, 4303355904 bytes, 8404992 sectors
pvcreate command earlier, LVM took over the raw, unpartitioned block devices (sda, sdb, sdc) directly, rather than writing a traditional partition table like MBR or GPT.
fdisk and gparted look for standard partition tables to tell them where a drive starts and ends. LVM bypasses this entirely:- Direct Disk Ownership: LVM wipes out the standard partition header area and writes its own metadata directly to the very first sectors of the raw disk.
- Simplified Layout: Because we are striping these drives together, creating individual partitions (like
sda1,sdb1) is an unnecessary extra layer of management. By giving LVM the raw disks, it can seamlessly manage the entire drive boundary.
fdisk doesn’t natively parse LVM metadata structures, it will report that the disks look empty. To see the true reality of your storage, you must use LVM-specific inspection commands instead:- To see the raw disks LVM owns:
pvdisplayorpvs - To see your combined master pool:
vgdisplayorvgs - To see the striped swap volume we just carved out:
lvdisplayorlvs"
Take this with a grain of sand I do not know if its true but seems that way.
root@g9-pve:~# fdisk -l Disk /dev/sda: 223.57 GiB, 240057409536 bytes, 468862128 sectors Disk model: KINGSTON SA400S3 Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disk /dev/sdb: 223.57 GiB, 240057409536 bytes, 468862128 sectors Disk model: KINGSTON SA400S3 Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disk /dev/sdc: 223.57 GiB, 240057409536 bytes, 468862128 sectors Disk model: KINGSTON SA400S3 Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes root@g9-pve:~# pvs PV VG Fmt Attr PSize PFree /dev/sda pve-fast lvm2 a-- <223.57g 222.23g /dev/sdb pve-fast lvm2 a-- <223.57g 222.23g /dev/sdc pve-fast lvm2 a-- <223.57g 222.23g /dev/sde3 pve lvm2 a-- <29.22g 3.63g root@g9-pve:~# vgs VG #PV #LV #SN Attr VSize VFree pve 1 2 0 wz--n- <29.22g 3.63g pve-fast 3 1 0 wz--n- <670.70g 666.69g root@g9-pve:~# lvs LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert data pve twi-a-tz-- <10.79g 0.00 1.58 root pve -wi-ao---- <12.80g fast-swap pve-fast -wi-ao---- <4.01g
Back to Storage
1: Recreate it with 99% of the pool space
99%FREE instead of 100%FREE, we guarantee there is plenty of room left over for the metadata engines while still satisfying the stripe alignment requirements.lvcreate -l 99%FREE -i 3 -I 64k -n fast-data pve-fast
2: Convert it to a Thin Pool
lvconvert --type thin-pool pve-fast/fast-data
pvesm add lvmthin Striped-SSDs --vgname pve-fast --thinpool fast-data
The conversion went through smoothly, and the high-speed storage tier is now online in the Proxmox GUI under the name Striped-SSDs
Quick Sequential I/O test:
root@g9-pve:~# swapoff /dev/pve-fast/fast-swap root@g9-pve:~# dd if=/dev/zero of=/dev/pve-fast/fast-swap bs=1M count=2000 status=progress conv=fdatasync 2000+0 records in 2000+0 records out 2097152000 bytes (2.1 GB, 2.0 GiB) copied, 3.59334 s, 584 MB/s root@g9-pve:~# swapon /dev/pve-fast/fast-swap swapon: /dev/mapper/pve--fast-fast--swap: read swap header failed root@g9-pve:~# mkswap /dev/pve-fast/fast-swap Setting up swapspace version 1, size = 4 GiB (4303351808 bytes) no label, UUID=aa6557d6-626b-4680-9741-2b63a1f55a13 root@g9-pve:~# swapon /dev/pve-fast/fast-swap
More Storage
Yes even more storage, while we used LVM to stripe across our 3 SSDs. We are going to use Ext4 on the 4TB Drive to host ISOs, or large disk virtual drives on the VMs.
1. Create a Standard Partition Table
parted -s /dev/sdd mklabel gpt mkpart primary ext4 0% 100%
2. Format the Partition as Ext4
/dev/sdd1) with the standard Linux Ext4 filesystem. This handles sequential data streams beautifully on mechanical platters.mkfs.ext4 /dev/sdd1
3. Create a Mount Point and Mount the Drive
mkdir -p /mnt/pve/mass-storage
mount /dev/sdd1 /mnt/pve/mass-storage
fstab). Run this command to fetch the drive’s unique ID and automatically write the mount rule:echo "/dev/sdd1 /mnt/pve/mass-storage ext4 defaults,noatime,nofail 0 2" >> /etc/fstab
noatime eliminates unnecessary write cycles to track when files are read, and nofail ensures your Proxmox host still boots perfectly even if the 4TB drive is unplugged).5. Register the Directory in the Proxmox GUI
pvesm add dir Mass-Storage --path /mnt/pve/mass-storage --content backup,iso,images
Verify Your Entire Server Storage Layout
df -h /mnt/pve/mass-storage
root@g9-pve:~# parted -s /dev/sdd mklabel gpt mkpart primary ext4 0% 100% root@g9-pve:~# mkfs.ext4 /dev/sdd1 mke2fs 1.47.2 (1-Jan-2025) Creating filesystem with 976754176 4k blocks and 244195328 inodes Filesystem UUID: 84d88803-a246-4067-8cec-3a93ba188169 Superblock backups stored on blocks: 32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208, 4096000, 7962624, 11239424, 20480000, 23887872, 71663616, 78675968, 102400000, 214990848, 512000000, 550731776, 644972544 Allocating group tables: done Writing inode tables: done Creating journal (262144 blocks): done Writing superblocks and filesystem accounting information: done root@g9-pve:~# mkdir -p /mnt/pve/mass-storage root@g9-pve:~# mount /dev/sdd1 /mnt/pve/mass-storage mount: (hint) your fstab has been modified, but systemd still uses the old version; use 'systemctl daemon-reload' to reload. root@g9-pve:~# systemctl daemon-reload root@g9-pve:~# echo "/dev/sdd1 /mnt/pve/mass-storage ext4 defaults,noatime,nofail 0 2" >> /etc/fstab root@g9-pve:~# dd if=/dev/zero of=/mnt/pve/mass-storage/zerofile bs=1M status=progress conv=fdatasync 78979792896 bytes (79 GB, 74 GiB) copied, 293 s, 270 MB/s root@g9-pve:~# dd if=/dev/zero of=/mnt/pve/mass-storage/zerofile bs=1M status=progress oflag=direct 2867855360 bytes (2.9 GB, 2.7 GiB) copied, 33 s, 86.9 MB/s
Summary: Proxmox VE Storage Architecture: Maximizing Performance and RAM with a Non-Cached Controller
Phase 1: Protecting the Boot Media and Reclaiming Swap
pve-swap volume from the SD card. To preserve a host safety net without introducing latency or deadlocks, we moved the swap partition to our incoming solid-state pool. Crucially, this swap space was created as a fixed, pre-allocated “Thick” LVM volume rather than being nested inside a thin data pool, ensuring kernel stability under unexpected memory spikes.Phase 2: The Performance Tier (3-Disk LVM-Thin Stripe)
sda, sdb, sdc) into a single LVM Volume Group named pve-fast.-i 3). This acts as a high-efficiency software RAID0 array directly inside the Linux kernel. During configuration, we encountered a classic LVM hurdle: allocating 100% of the remaining pool to a raw data container left 0 extents behind for the metadata tracker, causing the LVM-Thin conversion to fail. Re-provisioning the container at 99%FREE provided the required breathing room for the tracking database while maintaining perfect alignment across the three controllers.dd directly against the raw striped blocks clocked in at a blistering 584 MB/s, successfully compounding the bandwidth of our independent controllers.Phase 3: The Mass Storage Tier (Zero-RAM Spindle Directory)
sdd), we chose to completely bypass LVM and ZFS, formatting it directly as a standard Ext4 Directory partition mapped straight to the Proxmox dashboard.Phase 4: Benchmarking and the Reality of Caching
- The Buffered Test (
conv=fdatasync): After an initial RAM-buffered burst, the sequential write stream stabilized at an impressive 270 MB/s. This is significantly faster than the drive’s raw hardware capability. The boost is entirely driven by Ext4 optimizations like Delayed Allocation (delalloc) and sequential extents, which neatly arrange incoming data blocks on the fast, outer edge of the empty platter. - The Direct I/O Test (
oflag=direct): To expose the raw physical limits of the drive, we bypassed the Linux kernel’s RAM page cache completely. Stripped of its file-system optimizations, the performance leveled off at 100 MB/s, exposing the exact mechanical floor of the spindle and proving how vital the filesystem’s caching layer is for normal operation.