Should You Disable Transparent HugePages on Linux? enabled, defrag, and madvise

Should you disable Transparent HugePages on Linux? Compare enabled always, madvise, and never, understand the defrag knob, see benchmark results for THP collapse and fork COW, and get workload-specific guidance for Redis, PostgreSQL, and general servers.

Published

Updated

Read time 14 min read

Reviewed byDeepak Prasad

Linux Transparent HugePages enabled defrag madvise decision guide

Transparent HugePages (THP) is a Linux memory feature. Normally your programs use small 4 KiB memory pages. THP lets the kernel group those into larger 2 MiB pages when it helps—mainly to speed up programs that scan a lot of RAM. The trade-off is extra work in the kernel (rearranging memory) and surprises for apps that fork (like Redis during a background save).

This guide answers a common question: should you disable THP? You will learn what the enabled and defrag settings do, how always, madvise, and never differ, and what I measured on Ubuntu 25.04.

For how THP differs from manually reserved HugePages (vm.nr_hugepages), see HugePages vs Transparent HugePages. To check whether THP is in use, see check Transparent HugePages in Linux.

Tested on: Ubuntu 25.04 (Plucky Puffin); kernel 7.0.0-27-generic.

NOTE
Changing THP affects the whole machine. Try on a lab VM first; when you are done, restore defaults (madvise is the default on this Ubuntu install).

Quick answer: should you disable THP?

Situation What I would do
Normal Ubuntu / RHEL server or desktop Leave the default (madvise on both knobs) unless you see a real problem
Redis, or a database on its own server where the vendor says turn THP off Set enabled and defrag to never
PostgreSQL with huge_pages=on Turn THP off and size static HugePages separately — configure HugePages
Big batch job that reads a huge chunk of RAM in order Try madvise or test always; confirm with AnonHugePages
You read “always disable THP” on an old blog Check your kernel default first — modern madvise is not the same as old always

You turn THP off mainly for predictable latency on databases that fork and save to disk—not because huge pages are always bad.

When you are unsure:

  • Start with your distro default (madvise on Ubuntu 25.04) unless the app vendor names your version.
  • Move to never on both enabled and defrag if you see slow saves, memory spikes during fork, or Redis-style warnings—not because of a generic rule from years ago.
  • Use static HugePages when the app documents them (PostgreSQL huge_pages=on, some Kubernetes pods)—THP does not replace that.

Quick command summary

Task Command
THP mode cat /sys/kernel/mm/transparent_hugepage/enabled
Defrag policy cat /sys/kernel/mm/transparent_hugepage/defrag
THP page size cat /sys/kernel/mm/transparent_hugepage/hpage_pmd_size (2097152 = 2 MiB)
System THP usage grep -i AnonHugePages /proc/meminfo
Per-process THP grep AnonHugePages /proc/PID/smaps_rollup
Set mode (runtime) echo madvise | sudo tee /sys/kernel/mm/transparent_hugepage/enabled
Disable THP (runtime) echo never | sudo tee /sys/kernel/mm/transparent_hugepage/{enabled,defrag}
khugepaged running? ps aux | grep khugepaged

THP in one minute (and where to read more)

  • Linux splits memory into pages (chunks). The usual size is 4 KiB. The CPU can only track a limited number of page translations at once; huge 2 MiB pages mean fewer lookups when you walk through a large array.
  • Transparent means the kernel tries to do this for you—you do not reserve a special memory pool up front (that is static HugePages, a different feature).
  • A background kernel thread called khugepaged looks for chances to merge small pages into big ones when policy allows.
  • The defrag setting controls how hard the kernel shuffles free memory to find a contiguous 2 MiB block. Aggressive shuffling can slow down your program while it waits.

THP is not the same as HugePages_Total in /proc/meminfo or vm.nr_hugepages—those are for apps that ask for reserved huge pages (PostgreSQL, DPDK, some Kubernetes pods).

More depth: HugePages vs Transparent HugePages and Linux memory management overview.


Two sysfs knobs: enabled and defrag

Linux exposes THP settings as simple text files under /sys/kernel/mm/transparent_hugepage/. Two files matter day to day:

  • enabled — whether program memory may use transparent huge pages (always, madvise, or never)
  • defrag — how eagerly the kernel rearranges RAM to build a 2 MiB page

They work independently. You can leave enabled at madvise while defrag still changes how long allocations take—which is why database guides often mention both files.

enabled — who gets huge pages?

Read both files when you troubleshoot. The value in square brackets is what is active now:

bash
cat /sys/kernel/mm/transparent_hugepage/enabled
cat /sys/kernel/mm/transparent_hugepage/defrag
text
always [madvise] never
always defer defer+madvise [madvise] never

On this host enabled is madvise and defrag is madvise. The other words are values you can write with echo madvise | sudo tee ….

enabled value In plain English
always The kernel may use huge pages for eligible program memory automatically
madvise Huge pages are off by default; only programs that ask get them
never No transparent huge pages for normal program memory

What each mode feels like:

  • always — large long-lived programs (some JVM heaps, analytics jobs) may get 2 MiB pages without asking. Can speed up big memory scans, but uses memory in bigger chunks and can surprise fork-heavy apps.
  • madvise — default on this host. Most programs stay on 4 KiB pages unless they opt in. For most general servers this behaves like “THP is off until something wants it.”
  • never — THP is fully off for normal memory. Common on dedicated Redis nodes. Does not set up static HugePages—PostgreSQL huge_pages=on still needs vm.nr_hugepages (configure HugePages).

defrag — how hard the kernel fights for a 2 MiB block

Even when huge pages are allowed, the kernel needs 512 contiguous small pages (2 MiB) free together. Fragmented RAM may require compaction—moving other pages aside. The defrag file controls how much of that work happens while your program is waiting.

defrag value In plain English
always May compact memory during your allocation—your thread can pause
defer / defer+madvise More work happens later in the background (khugepaged)
madvise Default here—aggressive work mainly when a program asked for huge pages
never No compaction for THP

Simple way to remember:

  • enabled answers: “May this memory use huge pages?”
  • defrag answers: “How hard should the kernel rearrange RAM to make that happen?”

With defrag=always, a single large allocation can block while the kernel moves pages. On this host that was 349 ms versus 136 ms for the same 512 MiB test with defrag=madvise (see benchmark below).

About khugepaged:

  • Shows up in ps aux when THP is allowed
  • Merges already-allocated small pages into big ones when the system is idle or under light load
  • Heavy activity under load can line up with latency spikes—check before you tune

How the two settings work together

Many guides only mention enabled. When a database doc says “disable THP,” it usually means set both to never so neither merging nor compaction runs on the hot path.

Goal Typical enabled Typical defrag
Distro default (mixed server) madvise madvise
Dedicated Redis / latency-sensitive DB never never
Benchmarking maximum THP always defer or madvise (avoid always unless you accept pauses)

After you change either file, check grep AnonHugePages /proc/meminfo under real load—not only at idle. Per-process checks: check Transparent HugePages in Linux.


Check what is active today

On this host before testing:

bash
grep -iE 'AnonHuge|HugePages_Total' /proc/meminfo
ps aux | grep '[k]hugepaged'
text
AnonHugePages:      2048 kB
HugePages_Total:       0
root ... [khugepaged]

How to read that output:

  • AnonHugePages: 2048 kB — a little THP is in use system-wide (normal on a running machine).
  • HugePages_Total: 0 — no reserved static huge-page pool; that number does not tell you whether THP is on or off.
  • [khugepaged] running — the merge thread is available; with enabled=never it does much less.

Full walkthrough: check Transparent HugePages in Linux.


Benchmark 1: enabled = never vs madvise vs always

I ran the same Python test on this host for each enabled mode (defrag=madvise throughout): allocate 256 MiB and ask for huge pages, touch every 4 KiB, then time a sequential read through the block.

bash
# Example: set mode, then run workload (see sections below for scripts)
echo madvise | sudo tee /sys/kernel/mm/transparent_hugepage/enabled
enabled AnonHugePages after 256 MiB opt-in alloc Time to walk all pages
never 0 KiB (process) 0.0203 s
madvise 208896 KiB (~204 MiB) 0.0103 s
always 249856 KiB (~244 MiB) 0.0193 s

What that means on this host:

  • never — the program’s request for huge pages was ignored; AnonHugePages stayed at 0. THP is fully off for this test.
  • madvise — about 204 MiB of the 256 MiB block became huge pages; the full scan took about half as long as never on this VM.
  • always — even more memory used huge pages (~244 MiB), but the scan was not faster than madvise here. System-wide always is not automatically a win.
  • Takeaway — huge pages can help when you read a lot of RAM in order, but you should measure on your workload instead of assuming always is best.

Allocation and page-walk check (enabled=madvise):

bash
echo madvise | sudo tee /sys/kernel/mm/transparent_hugepage/enabled
python3 <<'PY'
import ctypes, mmap, time
libc = ctypes.CDLL("libc.so.6")
size = 256 * 1024 * 1024
buf = mmap.mmap(-1, size, prot=mmap.PROT_READ|mmap.PROT_WRITE,
                flags=mmap.MAP_PRIVATE|mmap.MAP_ANONYMOUS)
addr = ctypes.c_void_p(ctypes.addressof(ctypes.c_char.from_buffer(buf)))
libc.madvise(addr, size, 14)  # MADV_HUGEPAGE
view = memoryview(buf)
for i in range(0, size, 4096):
    view[i] = 1
with open("/proc/self/smaps_rollup") as f:
    for line in f:
        if line.startswith("AnonHugePages:"):
            print(line.strip())
start = time.perf_counter()
s = sum(view[i] for i in range(0, size, 4096))
print(f"walk_seconds={time.perf_counter()-start:.4f}")
PY
text
AnonHugePages:      208896 kB
walk_seconds=0.0103

Benchmark 2: defrag impact on allocation latency

This test isolates defrag. I kept enabled=madvise (huge pages only when a program asks) and allocated 512 MiB twice—only defrag changed.

defrag Time to allocate and touch 512 MiB
madvise (default) 136 ms
always 349 ms

What that means on this host:

  • enabled did not change — both runs used madvise. The slowdown comes from defrag, not from turning on system-wide huge pages.
  • defrag=madvise (136 ms) — memory was set up without aggressive on-the-spot rearrangement.
  • defrag=always (349 ms) — about 2.6× slower. The kernel may shuffle other pages while your program waits for a 2 MiB block.
  • Same result, different wait — both runs successfully used 512 MiB. The difference is how long you wait, not whether allocation works.
  • Why DB guides care — hundreds of milliseconds on a large allocation can show up as query pauses or slow background saves. That is why “disable THP” often sets defrag=never as well as enabled=never.

To reproduce, run the script once with default defrag, then again after switching to always:

bash
echo madvise | sudo tee /sys/kernel/mm/transparent_hugepage/enabled
echo always | sudo tee /sys/kernel/mm/transparent_hugepage/defrag
time python3 <<'PY'
import ctypes, mmap
libc = ctypes.CDLL("libc.so.6")
size = 512 * 1024 * 1024
buf = mmap.mmap(-1, size, prot=mmap.PROT_READ|mmap.PROT_WRITE,
                flags=mmap.MAP_PRIVATE|mmap.MAP_ANONYMOUS)
addr = ctypes.c_void_p(ctypes.addressof(ctypes.c_char.from_buffer(buf)))
libc.madvise(addr, size, 14)
view = memoryview(buf)
for i in range(0, size, 4096):
    view[i] = 1
PY
# restore defrag default:
echo madvise | sudo tee /sys/kernel/mm/transparent_hugepage/defrag

Compare the real time from time with defrag=madvise first—you should see a similar gap on a memory-loaded host.


Benchmark 3: fork and copy-on-write (Redis-style)

Redis background saves (BGSAVE) and similar tools fork a child process while the parent keeps a large memory area. When memory uses 2 MiB pages, copy-on-write (COW)—copying a page only when someone writes to it—can happen in bigger chunks than with 4 KiB pages. That can inflate memory use during the save window.

I filled 400 MiB with an opt-in huge-page request, waited for merging, forked, and had the child rewrite 80 MiB:

bash
echo madvise | sudo tee /sys/kernel/mm/transparent_hugepage/enabled
python3 <<'PY'
import ctypes, mmap, os, time
libc = ctypes.CDLL("libc.so.6")
size = 400 * 1024 * 1024
buf = mmap.mmap(-1, size, prot=mmap.PROT_READ|mmap.PROT_WRITE,
                flags=mmap.MAP_PRIVATE|mmap.MAP_ANONYMOUS)
addr = ctypes.c_void_p(ctypes.addressof(ctypes.c_char.from_buffer(buf)))
libc.madvise(addr, size, 14)
view = memoryview(buf)
for i in range(0, size, 4096):
    view[i] = 1
for _ in range(3):
    time.sleep(1)
def ah():
    with open("/proc/self/smaps_rollup") as f:
        for line in f:
            if line.startswith("AnonHugePages:"):
                return line.strip()
print("parent_after_fill", ah())
pid = os.fork()
if pid == 0:
    for i in range(0, 80*1024*1024, 4096):
        view[i] = 2
    print("child_after_cow", ah())
    os._exit(0)
os.waitpid(pid, 0)
print("parent_after_child", ah())
PY
enabled Parent AnonHugePages after fill Child AnonHugePages after COW
never 0 KiB 0 KiB
madvise 409600 KiB (~400 MiB) 327680 KiB (~320 MiB)
always 409600 KiB 327680 KiB

Example output with enabled=madvise:

text
parent_after_fill AnonHugePages:      409600 kB
child_after_cow AnonHugePages:      327680 kB
parent_after_child AnonHugePages:      409600 kB

With enabled=never, the same script reported AnonHugePages: 0 kB at each step.

What the fork test shows:

  • Parent fills 400 MiB — with madvise or always, most of it becomes huge pages (~400 MiB in AnonHugePages) after a few seconds.
  • Child rewrites 80 MiB after fork — COW breaks sharing; the child can still hold hundreds of MiB as huge pages because copies happen in 2 MiB units when THP is active.
  • With never — the same pattern stays on small pages; AnonHugePages stays at 0 KiB.
  • Redis angle — during BGSAVE, fork plus huge-page memory can spike RSS and lengthen the save even when normal traffic looks fine.
  • Lab script, real mechanism — this is not Redis itself, but it shows why vendors warn about THP and fork-based persistence.

What to use for common workloads

Workload Suggested setting Why in plain terms
General Linux VM / app server enabled=madvise, defrag=madvise Huge pages only when a program asks—safe default
Redis (dedicated server) enabled=never, defrag=never Fork + background save; Redis warns at startup if THP is too aggressive
PostgreSQL THP off; static huge pages if you want them Predictable reserved pool—configure HugePages
MySQL / MariaDB Often never on dedicated DB servers Historical latency spikes from memory rearrangement
MongoDB Check your version’s docs Advice changed between releases
JVM / analytics madvise or test always Large heaps may benefit if pauses are acceptable
Kubernetes worker Node THP policy + static huge pages for pods that request them THP ≠ hugepages-2Mi in the pod spec—comparison guide
NOTE
Redis’s log sometimes suggests madvise as a fix—that stops system-wide always but still allows opt-in huge pages. Many operators set never on dedicated Redis hosts for simplicity.

Change THP at runtime

bash
# Balanced (Ubuntu default on this host)
echo madvise | sudo tee /sys/kernel/mm/transparent_hugepage/enabled
echo madvise | sudo tee /sys/kernel/mm/transparent_hugepage/defrag

# Disable THP completely
echo never | sudo tee /sys/kernel/mm/transparent_hugepage/enabled
echo never | sudo tee /sys/kernel/mm/transparent_hugepage/defrag

# Verify
cat /sys/kernel/mm/transparent_hugepage/enabled
grep AnonHugePages /proc/meminfo

After you write sysfs:

  • Changes apply immediately to the whole machine—there is no per-app toggle in these files.
  • Re-read enabled and defrag and confirm the bracketed value.
  • Run your app, then check AnonHugePages—idle systems may show 0 KiB even when THP is allowed.
  • Sysfs changes are lost on reboot unless you add GRUB, systemd, or tuned persistence (next section).

For related virtual-memory tuning (swap, not THP), see swap and swappiness guide and sysctl for high performance servers.


Make changes persistent (overview)

Method When to use
GRUB transparent_hugepage=never Boot-wide policy on bare metal / VMs
systemd oneshot unit writing sysfs Ubuntu/Debian without GRUB edits
tuned profile RHEL-family managed hosts

Choosing a persistence method:

  • GRUB transparent_hugepage=never — policy is set at boot before your services start; good when every workload on the box should share one rule.
  • systemd oneshot unit — writes sysfs early on Ubuntu/Debian without editing GRUB; good for config-management-driven hosts.
  • tuned — fits RHEL sites that already manage kernel tuning through tuned-adm.

Step-by-step GRUB procedures:

Pick one persistence method—do not stack GRUB and systemd units that fight each other.


Troubleshooting

Symptom Likely cause What to try
AnonHugePages > 0 but enabled shows [never] Shared-memory THP or another size setting Check shmem_enabled and hugepages-2048kB/enabled; see check THP guide
Latency spikes after kernel upgrade defrag default or compaction behavior changed Compare defrag; try never on DB nodes
PostgreSQL will not use huge pages THP on but no reserved pool Turn THP off; reserve static pages per configure HugePages
Opt-in mode but no AnonHugePages Mapping too small, not touched, or fragmented RAM Touch pages; wait for khugepaged; read smaps_rollup
Redis warning at startup enabled is always Set madvise or never per table above

References


Summary

You do not need to disable Transparent HugePages on every Linux box in 2026. Ubuntu and RHEL defaults (madvise) are a sensible middle ground: huge pages stay off until a program asks, or until policy allows merging.

Turn THP fully off (never on both enabled and defrag) when your database vendor says so, or when you measure fork slowdowns, long pauses during big allocations, or unwanted AnonHugePages on a dedicated Redis or DB server. On a general app server, keep madvise unless profiling shows a clear benefit from always.

On my test host, opting in to huge pages created ~400 MiB of AnonHugePages and roughly halved a sequential memory scan versus never, while defrag=always made the same allocation about 2.5× slower than defrag=madvise. THP is not a substitute for static HugePages—size vm.nr_hugepages separately when PostgreSQL or Kubernetes explicit huge pages are required.


Frequently Asked Questions

1. Should I disable Transparent HugePages on Linux?

Not on every host. Ubuntu and RHEL defaults use madvise, which is a fair balance. Use never for Redis and other fork-heavy databases when the vendor doc says so, keep madvise on mixed servers, and only try always if you measured a real throughput win.

2. What is the difference between enabled and defrag for THP?

enabled decides whether program memory may use transparent huge pages at all (always, madvise, never). defrag decides how hard the kernel rearranges RAM to build a 2 MiB page—aggressive defrag can slow allocations even when enabled is madvise.

3. Is madvise the same as disabling THP?

No. madvise means huge pages are off by default, but programs can opt in. On my host that produced about 400 MiB of AnonHugePages for a 400 MiB mapping; with enabled=never the same test stayed at 0 MiB.

4. What is the difference between THP and static HugePages?

THP is managed by the kernel and shows up as AnonHugePages. Static HugeTLB uses vm.nr_hugepages and must be reserved up front—PostgreSQL huge_pages=on is not the same knob. See HugePages vs Transparent HugePages for the full comparison.

5. Why does Redis warn about Transparent HugePages?

Background saves fork a child process. Huge pages make copy-on-write work in bigger chunks, which can spike memory during the save. Redis warns when THP is not madvise or never; many operators set both enabled and defrag to never on dedicated Redis nodes.

6. Do I need to set both enabled and defrag to never?

When a database doc says disable THP, set both to never. defrag=always alone can still pause your program while the kernel compacts memory—on my host a 512 MiB allocation took 349 ms with defrag=always versus 136 ms with defrag=madvise.

7. How do I check if THP is active on my system?

Read /sys/kernel/mm/transparent_hugepage/enabled and defrag—the value in square brackets is active. Confirm usage with grep AnonHugePages /proc/meminfo. See check Transparent HugePages in Linux for commands.

8. How do I disable THP permanently?

At runtime: echo never | sudo tee /sys/kernel/mm/transparent_hugepage/enabled and defrag. After reboot: GRUB transparent_hugepage=never, a systemd unit, or tuned—see disable THP on RHEL 7 or RHEL 8 GRUB guides.

9. Is disabling THP outdated advice on kernel 6.x?

Partly. Modern madvise defaults are safer than old always. Vendor database guidance still often says never for fork-heavy latency-sensitive workloads, but blind never on every server is no longer the only correct answer.

10. Does never stop khugepaged?

When THP is fully never, khugepaged has little to do. With madvise it stays available for opt-in regions. Check with ps aux | grep khugepaged and AnonHugePages after your workload runs.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive …