Every virtual machine on a Proxmox VE host shares one resource that almost nobody tunes: the size of the memory pages QEMU hands to the guest. By default, Linux manages RAM in 4 KiB pages. For a VM with a few gigabytes of RAM, that means millions of page table entries the CPU has to walk and cache in its Translation Lookaside Buffer (TLB) every time a guest touches a new region of memory. For most workloads this overhead is invisible. For databases, in-memory caches, DPDK-based network functions, and anything else that is sensitive to memory latency and TLB misses, it is a real, measurable tax — and it is one of the few performance knobs left on the table after you have already tuned CPU pinning and storage.

HugePages fix this by handing QEMU much bigger pages — 2 MiB or 1 GiB instead of 4 KiB — which collapses the number of page table entries by three to six orders of magnitude and dramatically reduces TLB pressure. Proxmox VE has built-in support for both Transparent HugePages (a Linux kernel feature that requires no VM-specific configuration) and static HugePages (memory reserved at boot and explicitly assigned to a VM, which is what most performance-sensitive deployments actually want). This guide covers both, plus the NUMA-aware configuration you need on multi-socket hosts, the interaction between HugePages and memory ballooning, and the verification steps that confirm a VM is actually using the pages you reserved for it.

What You Will Learn

  • The difference between Transparent HugePages (THP) and static HugePages, and when each one applies
  • How to check whether THP is already active on your Proxmox VE host
  • How to reserve static 2 MiB or 1 GiB HugePages at boot via GRUB kernel parameters
  • How to reserve HugePages per NUMA node on multi-socket hardware
  • How to configure a VM to consume static HugePages with qm set or /etc/pve/qemu-server/<vmid>.conf
  • Why memory ballooning and static HugePages cannot be used together, and what to disable
  • How to verify a running VM is actually backed by HugePages instead of falling back to normal pages
  • Troubleshooting VMs that fail to start after enabling HugePages, and the fragmentation problem that causes it
  • A practical decision guide for when HugePages are worth the operational overhead

Prerequisites

  • A Proxmox VE 8.x or 9.x host with root or SSH access
  • Comfort editing /etc/default/grub and rebooting the host — static HugePages require a reboot to take effect
  • Enough free, unfragmented RAM to reserve for HugePages on top of what the host itself, ZFS ARC, or Ceph OSDs already consume
  • For 1 GiB pages, a CPU that supports the pdpe1gb flag (essentially every server CPU from the last decade; check with grep pdpe1gb /proc/cpuinfo)
  • On multi-socket hosts, some familiarity with which NUMA node your VM's vCPUs and PCI devices are attached to

Transparent HugePages vs. Static HugePages

These solve the same underlying problem in two very different ways, and confusing them is the most common mistake people make when they first look into this.

Transparent HugePages (THP) is a Linux kernel feature, enabled by default on Debian (and therefore on Proxmox VE), that opportunistically promotes contiguous 4 KiB pages into 2 MiB pages in the background for any process on the system — including QEMU. You do not configure anything per-VM for THP; it either works transparently or it does not, depending on whether the kernel can find contiguous physical memory to promote. Check the current host-wide setting with:

cat /sys/kernel/mm/transparent_hugepage/enabled
# [always] madvise never   -- default on Proxmox VE, THP applies to every process

THP is "free" in the sense that it requires zero VM configuration, but it comes with two caveats that matter for latency-sensitive workloads. First, the khugepaged kernel thread that scans memory and merges pages into hugepages runs periodically and can introduce brief latency spikes while it works — this is the main complaint in the long-running Proxmox forum thread on THP tuning, and it is why many production deployments set the sysfs defrag mode to madvise instead of the default always, so promotion only happens where a process explicitly requests it rather than continuously across the whole host:

echo madvise > /sys/kernel/mm/transparent_hugepage/enabled
echo madvise > /sys/kernel/mm/transparent_hugepage/defrag

Second, and more importantly, THP gives you no guarantee. If the host's memory has fragmented after weeks of uptime, the kernel simply falls back to 4 KiB pages for that allocation, silently, with no error and no visible warning in Proxmox. For anything where you actually need the pages to be huge — not just "huge when convenient" — you want static HugePages instead.

Static HugePages are reserved explicitly at boot time, out of a specific pool the kernel sets aside and will not touch for anything else. Once reserved, that memory is pinned: it cannot be swapped, cannot be used by other processes, and is guaranteed to be contiguous. A VM configured to use static HugePages either gets backed entirely by that reserved pool or fails to start — there is no silent fallback. This determinism is exactly why static HugePages are the standard recommendation for databases, SR-IOV/DPDK network appliances, and any VM where you are also doing CPU pinning and want end-to-end predictable performance.

Step 1: Decide How Much Memory to Reserve

Static HugePages are removed from the pool of memory the host can use for anything else — including its own page cache, ZFS ARC, or other VMs. Before reserving anything, work out a budget:

  • Leave at least 2 GiB (more on hosts running ZFS or Ceph) for the host OS itself
  • If you run ZFS, remember zfs_arc_max memory is separate from and competes with your HugePages budget — set both explicitly rather than relying on defaults
  • Only reserve HugePages for the VMs that actually need them; leave the rest of the host's RAM as normal pages for everything else, including VMs using ballooning
  • Round up to the VM's configured memory value — a VM with memory: 8192 needs at least 8192 MiB of HugePages available, and Proxmox will refuse to start it otherwise

You also need to pick a page size. 2 MiB pages are easier to reserve because the kernel can usually find enough contiguous 2 MiB regions even on a host that has been running for a while. 1 GiB pages give you an even bigger reduction in TLB pressure and are the standard choice for DPDK and other kernel-bypass networking workloads, but they can only reliably be reserved at boot, before memory has had a chance to fragment — reserving 1 GiB pages on a live, long-running host frequently fails outright.

Step 2: Reserve Static HugePages via GRUB

Check what your hardware supports first:

ls /sys/devices/system/node/node0/hugepages/
# hugepages-1048576kB  hugepages-2048kB

Then edit /etc/default/grub and add hugepage parameters to GRUB_CMDLINE_LINUX_DEFAULT. On a single-socket host reserving sixteen 1 GiB pages (16 GiB total):

GRUB_CMDLINE_LINUX_DEFAULT="quiet default_hugepagesz=1G hugepagesz=1G hugepages=16"

For 2 MiB pages instead, swap the size and scale the count accordingly (2 MiB pages need roughly 512x the count for the same total memory):

GRUB_CMDLINE_LINUX_DEFAULT="quiet default_hugepagesz=2M hugepagesz=2M hugepages=8192"

On a multi-socket host, reserve pages per NUMA node rather than as one global pool, so each node has local HugePages available instead of forcing cross-socket memory access. The format is a comma-separated list of node:count pairs, using the same hugepages= parameter:

GRUB_CMDLINE_LINUX_DEFAULT="quiet default_hugepagesz=1G hugepagesz=1G hugepages=0:16,1:16"

Apply the change and reboot — this is not optional, static HugePages can only be reserved reliably at boot before the kernel has had a chance to fragment memory:

update-grub
reboot

Step 3: Verify the Reservation Took Effect

After the host comes back up, confirm the pool was actually allocated. A shortfall here almost always means the requested amount didn't fit contiguously, and you need to lower the count or switch to 2 MiB pages:

grep Huge /proc/meminfo
# HugePages_Total:      16
# HugePages_Free:       16
# HugePages_Rsvd:        0
# HugePages_Surp:        0
# Hugepagesize:    1048576 kB

On a NUMA host, break the allocation down per node to confirm it landed where you expected:

numastat -cm | egrep "Huge|Node|MemFree"

HugePages_Total matching what you requested and HugePages_Free equal to HugePages_Total (before any VM has started) both need to be true. If HugePages_Total is lower than what you put in GRUB, the kernel could not find enough contiguous memory — this is the fragmentation failure mode static HugePages are supposed to avoid, and it means the reservation needs to happen even earlier in boot, or you need to reduce the request.

Step 4: Configure the VM to Use HugePages

With the pool reserved, point a specific VM at it. The hugepages option accepts three values: 1024 for 1 GiB pages, 2 for 2 MiB pages, or any, which tells Proxmox to prefer 1 GiB pages and silently fall back to 2 MiB if a contiguous 1 GiB region isn't available. For predictable behavior, set the size explicitly rather than using any.

qm set 101 --hugepages 1024

If you're using 1 GiB pages, also make sure the VM's CPU type exposes the pdpe1gb flag so the guest kernel can address 1 GiB pages itself:

qm set 101 --cpu host,flags=+pdpe1gb

By default, Proxmox releases a VM's HugePages back to the host pool the moment the VM shuts down. If you restart the same VM frequently and want it to reclaim the same pages quickly rather than re-competing for a shrinking, increasingly fragmented pool, set keephugepages so the pages stay reserved across shutdowns:

qm set 101 --keephugepages 1

The equivalent lines in /etc/pve/qemu-server/101.conf look like this:

memory: 8192
hugepages: 1024
keephugepages: 1
cpu: host,flags=+pdpe1gb

Start (or restart) the VM, then immediately re-check /proc/meminfo. HugePages_Free should have dropped by roughly the VM's configured memory — that drop is your confirmation the VM is actually backed by HugePages rather than having silently fallen back:

grep Huge /proc/meminfo

Step 5: NUMA-Aware VM Configuration

Reserving HugePages per NUMA node only pays off if the VM's vCPUs are also pinned to that same node — otherwise QEMU may still end up satisfying memory requests from a node other than the one your cores are running on, which reintroduces the exact cross-socket latency you were trying to eliminate. Enable NUMA and pin a VM to a specific node with:

qm set 101 --numa 1
qm set 101 --numa0 cpus=0-7,hostnodes=0,memory=8192,policy=bind

hostnodes selects the physical NUMA node to source memory from, memory should match the VM's total RAM so the whole allocation comes from that one node, and policy=bind makes the binding a hard requirement rather than a preference — the VM fails to start rather than silently spilling onto another node. Use policy=preferred instead if you'd rather allow a fallback than have the VM fail to start when the preferred node is under pressure.

Confirm which physical NUMA node your host's CPU cores and PCIe devices (particularly relevant if you're also doing GPU or NIC passthrough on this VM) belong to before picking hostnodes:

lscpu | grep NUMA
lspci -vvv -s <pci-id> | grep NUMA

HugePages and Memory Ballooning Don't Mix

This is the single most common misconfiguration people run into after enabling HugePages: the VM either fails to start, or memory ballooning silently stops doing anything useful. Static HugePages are pinned, fixed-size allocations — the whole point is that they cannot be resized while the VM is running. Memory ballooning, on the other hand, works by having the guest's virtio-balloon driver give pages back to the host on demand, which requires the underlying memory to be resizable. The two mechanisms are fundamentally incompatible, and Proxmox VE will not silently reconcile them for you.

Before enabling HugePages on a VM, disable ballooning explicitly by setting the balloon target equal to the VM's full memory allocation (which disables dynamic resizing):

qm set 101 --balloon 0

If you skip this step, at best the balloon driver simply has no effect because the backing memory can't shrink; at worst you get a VM that behaves inconsistently across host reboots depending on the order Proxmox applies these settings. If you're deciding between the two features for a given VM, HugePages and ballooning solve opposite problems — ballooning is about elastically packing more VMs onto a host with fluctuating memory needs, while HugePages are about guaranteeing consistent latency for one VM that needs it. Pick one per VM based on what that specific workload actually needs.

Troubleshooting

VM refuses to start after enabling HugePages

The most common cause is that HugePages_Free is lower than the VM's configured memory. Check with grep Huge /proc/meminfo — if Free is short, either another VM is already holding pages you were counting on, or the original GRUB reservation was smaller than you intended. QEMU's startup error in the Proxmox task log will usually mention being unable to allocate memory, which is the giveaway that this is a HugePages sizing problem rather than a general memory shortage.

The reservation shrinks over time

If you didn't set keephugepages: 1, pages are returned to the host pool on VM shutdown and re-requested on the next start. On a host that has been up for a long time, memory can fragment enough between those shutdown and restart events that the same reservation that worked yesterday fails today — this is significantly more likely with 1 GiB pages than 2 MiB ones, because larger contiguous regions are harder to reassemble once memory fragments. If this keeps happening, either switch that VM to 2 MiB pages or set keephugepages: 1 so the pages are never released in the first place.

THP and static HugePages both look "on" and you're not sure which one a VM is using

Any VM with an explicit hugepages: line in its config is using the static pool exclusively — THP does not apply on top of it. A VM with no hugepages line at all is relying purely on THP, which may or may not actually be promoting that VM's memory depending on fragmentation at the moment it was allocated. There's no in-between state; if you want the guarantee, you need the explicit config line.

Live migration behaves differently

Migrating a VM configured for static HugePages requires the destination node to have an equivalent HugePages reservation already in place — Proxmox does not create it on the fly. If you're running HugePages VMs in a cluster, apply the same GRUB reservation consistently across every node the VM might migrate to, not just the one it currently lives on.

When Static HugePages Are Actually Worth It

HugePages add real operational overhead: a reboot to apply, a fixed memory budget you have to manage by hand across every node in a cluster, and one more thing that can misconfigure and silently degrade performance instead of failing loudly. They are worth that overhead for a specific, fairly narrow set of workloads:

  • Databases with large buffer pools or shared memory segments (PostgreSQL with a large shared_buffers, MySQL/InnoDB with a large buffer pool) where TLB miss overhead shows up directly in query latency
  • DPDK or SR-IOV based network functions and virtual appliances, where 1 GiB pages combined with CPU pinning are close to mandatory for line-rate packet processing
  • Any VM you've already pinned to specific cores for latency reasons — HugePages is the natural next step once you've gone that far
  • Kubernetes worker nodes running latency-sensitive pods, where the same TLB-pressure argument applies one layer up

For general-purpose VMs — web servers, small internal services, most homelab workloads — the operational cost of static HugePages isn't worth it. Leave THP at its Debian default (or tune the defrag mode to madvise if you've noticed latency spikes from khugepaged) and spend the reboot-and-reservation effort only on the handful of VMs where memory latency is actually the bottleneck.

Frequently Asked Questions

Do I need to reboot every time I change the HugePages count?

Yes. The reservation happens at kernel boot via the GRUB parameters in this guide. Changing the hugepages= value in /etc/default/grub requires update-grub and a reboot to take effect — there is no live-resize equivalent for the boot-time pool.

Can I mix HugePages VMs and normal VMs on the same host?

Yes, and this is the common case. Reserve only as much static HugePages memory as your latency-sensitive VMs need, and leave the rest of the host's RAM as normal pages for everything else, including ballooning-enabled VMs.

What happens if I set hugepages: any and no 1 GiB pages are available?

Proxmox falls back to 2 MiB pages automatically. This is convenient but means you can't be certain which page size a given VM actually got without checking; for workloads where the difference matters, set the size explicitly instead of using any.

Does ZFS ARC compete with my HugePages reservation?

Indirectly. HugePages are reserved out of general RAM before ZFS or anything else gets a chance to use it, so a large HugePages reservation shrinks the RAM available for ARC. Set zfs_arc_max explicitly and account for it in your memory budget alongside the HugePages reservation, rather than letting ARC grow into whatever's left.

Is there a GUI option for any of this?

The per-VM hugepages, keephugepages, and NUMA settings are available under Hardware → Memory in the Proxmox VE web UI. The host-side GRUB reservation and THP defrag mode are not exposed in the GUI and must be set from the shell.

Conclusion

Transparent HugePages give every VM on a Proxmox VE host a small, unguaranteed performance boost for free. Static HugePages trade a bit of setup complexity — a GRUB parameter, a reboot, and a fixed memory budget — for a hard guarantee that a specific VM's memory is backed by large, contiguous, non-swappable pages. Combine that reservation with NUMA-aware placement and CPU pinning, remember to turn ballooning off on any VM you switch over, and you have the same memory-latency setup production database and NFV deployments rely on, running on a Proxmox VE host you already know how to operate.