Skip to content
← ENGINEERING NOTES
LINUX7 min read

Why Linux Stalls on Big Writes: Dirty Page Tuning

Copying a big file freezes your Linux box? Learn how dirty pages and writeback work, then tune vm.dirty_bytes and vm.dirty_background_bytes safely.

BY ASHLIN DARIUS GOVINDASAMY

You hit copy on a 40 GB file, the progress bar races to 100 percent in seconds, and then your whole desktop freezes while the 'finished' copy grinds on. Nothing is broken: that is Linux dirty page writeback doing exactly what it was designed to do, with limits that were sized for a different era. This guide explains how Linux dirty page writeback works, how to watch it live, and how to tune vm.dirty_bytes and vm.dirty_background_bytes so big writes stop stalling everything else.

Dirty pages: writes land in RAM first

When a program calls write(), Linux normally copies the data into the page cache and returns immediately. Those modified pages are called dirty, and background kernel flusher threads push them to disk later. This is why writes feel instant. The price is that the kernel needs rules for how much dirty data is allowed to pile up and when somebody must pay the bill.

There are two thresholds. The first is the background threshold: past it, the flusher threads start writing in the background and your program does not notice. The second is the hard threshold: past it, the process generating the writes is made to do writeback itself, and that is the stall you feel. The kernel's vm sysctl documentation describes dirty_ratio as the point at which a process generating disk writes will itself start writing out dirty data.

The knobs, straight from the kernel docs

  • vm.dirty_background_ratio: percentage of total available memory (free plus reclaimable pages) at which background flusher threads start writing.
  • vm.dirty_ratio: percentage of that same available memory at which a writing process starts writeback itself.
  • vm.dirty_background_bytes and vm.dirty_bytes: the absolute-size counterparts. Only one of each ratio/bytes pair is active at a time; writing one makes the other read as 0.
  • vm.dirty_expire_centisecs: how old dirty data must be, in hundredths of a second, before flusher threads write it out.
  • vm.dirty_writeback_centisecs: interval between flusher wakeups, in hundredths of a second. Zero disables periodic writeback altogether.

Here is the trap. The docs stress that the ratios are percentages of total available memory, which is not the same as total system memory. On a workstation with 64 GB of RAM, even a modest percentage allows gigabytes of dirty data. A slow USB stick or spinning disk that writes at tens of megabytes per second then needs minutes to drain it. That mismatch between RAM size and device speed is the whole story, and it is why absolute byte limits are usually a better fit than percentages.

Step 1: watch dirty pages live

bash
# Current sysctl values (a 0 means the bytes/ratio counterpart is in use)
sysctl vm.dirty_background_ratio vm.dirty_ratio \
       vm.dirty_background_bytes vm.dirty_bytes \
       vm.dirty_expire_centisecs vm.dirty_writeback_centisecs

# Live view: Dirty = waiting to be written, Writeback = being written now
watch -n 1 "grep -E '^(Dirty|Writeback):' /proc/meminfo"

# Thresholds the kernel actually computed, in pages
grep -E 'nr_dirty|nr_writeback|nr_dirty_threshold|nr_dirty_background_threshold' /proc/vmstat
Observe dirty data and the thresholds the kernel computed

In /proc/vmstat, nr_dirty_threshold and nr_dirty_background_threshold show the limits after the kernel has applied your settings to the memory it considers available, in pages (multiply by 4 for KiB with 4 KiB pages). If nr_dirty climbs toward nr_dirty_threshold while your app freezes, you have found your stall.

Step 2: reproduce the stall on purpose

bash
# In terminal A: watch dirty data
watch -n 1 "grep -E '^(Dirty|Writeback):' /proc/meminfo"

# In terminal B: write 8 GiB through the page cache
fio --name=burst --filename=/mnt/slow/testfile --rw=write --bs=1M \
    --size=8G --ioengine=psync --end_fsync=1

# In terminal C: a latency canary - small synchronous-ish writes
while true; do date +%s.%N; sleep 0.2; done | awk 'NR>1 && ($1-p)>0.5 {print "stall of", $1-p, "s"} {p=$1}'
A repeatable write test (replace the path with a slow device)

Terminal C prints a line whenever the loop is delayed by more than half a second. Run the test once with defaults and note both the peak Dirty figure and the stalls. That is your baseline.

QUICK CHECK

If you write 200000000 to vm.dirty_bytes, what happens to vm.dirty_ratio when you read it back?

Step 3: set byte limits sized for your slowest disk

Pick limits from your device, not from your RAM. A sensible rule of thumb: the hard limit should be a few seconds of the slow device's sustained write speed, and the background limit should be a fraction of that so the flushers start early. Measure your device speed first, then size the pair.

bash
# Example for a slow disk or USB stick: start background writeback at 64 MiB,
# make writers throttle at 256 MiB. Tune these to your own device.
sudo sysctl -w vm.dirty_background_bytes=$((64*1024*1024))
sudo sysctl -w vm.dirty_bytes=$((256*1024*1024))

# Confirm the ratio counterparts now read 0
sysctl vm.dirty_background_ratio vm.dirty_ratio

# Persist
sudo tee /etc/sysctl.d/90-writeback.conf >/dev/null <<'CONF'
vm.dirty_background_bytes = 67108864
vm.dirty_bytes = 268435456
CONF
sudo sysctl --system
Try limits at runtime, then persist them

Re-run the fio test. You should see Dirty plateau far lower, the copy progress bar tell the truth much sooner, and the canary stalls shrink. The trade-off is real: smaller limits mean less write buffering, so bursty workloads that previously finished 'instantly' now run closer to the device's true speed. That is honest behaviour, not a regression.

Gotchas worth knowing

  • Do not 'fix' stalls with drop_caches. The docs say it only drops clean caches and will not free dirty objects, and that using it outside testing or debugging is not recommended because recreating the cache can cost significant I/O and CPU.
  • Setting dirty_writeback_centisecs to 0 disables periodic writeback entirely. Do not do that on a machine you care about.
  • Lowering dirty_expire_centisecs makes data reach disk sooner, which narrows the loss window after a crash, at the cost of more frequent small writes.
  • dirty_bytes has a floor: the docs say values below two pages are ignored and the old configuration is retained.
  • Dirty limits are global. A noisy neighbour writing a huge file can stall unrelated processes, so on shared hosts combine this with cgroup v2 limits and PSI monitoring from our earlier posts.
›Why not just use the percentages?

Percentages scale with memory, and memory has grown far faster than the sustained write speed of many disks. A percentage that was a few hundred megabytes on an old machine becomes multiple gigabytes on a large one, and the time to drain it is dirty data divided by device throughput. Byte limits let you express the thing you actually care about: how many seconds of writing a process may be asked to absorb.

QUICK CHECK

Which situation does lowering vm.dirty_bytes mainly help?

Takeaway

Dirty pages are a loan from your future self: fast writes now, a stall later. Measure Dirty and Writeback while reproducing the problem, set vm.dirty_background_bytes and vm.dirty_bytes from your slowest device's real throughput, persist them in /etc/sysctl.d, and re-measure. If the stall disappears and your throughput on fast disks is intact, you are done.

SOURCES

  1. Documentation for /proc/sys/vm (vm.rst, source of the sysctl descriptions quoted above) — Linux kernel source tree

Frequently asked questions

Why does my Linux system freeze when copying large files?

Writes first fill the page cache with dirty pages. Once dirty data passes the hard limit, the writing process must perform writeback itself, which is slow on a slow device. Lowering vm.dirty_bytes and vm.dirty_background_bytes keeps that backlog small.

What is the difference between dirty_ratio and dirty_bytes?

dirty_ratio is a percentage of available memory, dirty_bytes is an absolute size. Only one of the pair is active at a time; setting one makes the other read as 0.

Is it safe to lower vm.dirty_ratio?

Yes, it is a runtime sysctl and does not risk data. It reduces write buffering, so bursty writes run closer to real device speed. Test on your workload first.

How do I see how much dirty data Linux has?

Read the Dirty and Writeback lines of /proc/meminfo, and nr_dirty with the computed thresholds in /proc/vmstat.

NEWSLETTER

Get the engineering notes in your inbox.

Kernels, GPUs, networks, AI infrastructure — and the things we broke along the way. No spam, one click to leave.

We store only your email address, to send you new articles. See our privacy policy.

TAGS

linuxpage cachewritebacksysctllatencystorage

RELATED

ENGINEERING NOTES

Things we built.Things we broke.Things we learned.