Linux Memory Management and Swap Configuration

Memory problems on Linux rarely start with the machine being literally out of RAM. They usually start with someone looking at free -h, seeing almost all memory marked as used, and assuming the kernel is leaking, the database is bloated, or swap is somehow the root of every evil. I have seen good servers rebooted for no reason because of that misunderstanding alone.

Linux is aggressive about using free RAM for cache, and that is usually a good thing. The hard part is learning the difference between healthy memory usage, reclaimable cache, real memory pressure, and the kind of sustained pressure that ends with the OOM killer shooting a process you cared about. That distinction matters more than any single tuning value.

In this guide I’ll walk through the tools I actually use, what swap is good for, when I tune vm.swappiness, how I read OOM events, and where I get cautious. If you already know the commands but want the operational context behind them, this is the part that tends to save time in production.

Start by reading memory correctly

The first command I run is still free -h, but I never stop there.

free -h

Typical output looks like this:

               total        used        free      shared  buff/cache   available
Mem:            31Gi       18Gi       1.2Gi       512Mi        12Gi        11Gi
Swap:          4.0Gi       256Mi       3.7Gi

The important field on modern systems is usually available, not free. free is just RAM with nothing in it at all. Linux tries hard not to leave RAM idle, so a low free value is normal. available is the kernel’s estimate of memory that can be used without heavy reclaim.

If I want the raw counters, I go straight to /proc/meminfo:

grep -E 'MemTotal|MemFree|MemAvailable|Buffers|Cached|SwapTotal|SwapFree|Dirty|AnonPages' /proc/meminfo

That gives a better picture of what is anonymous memory, what is cache, and how much swap headroom exists. For a live view I often use htop, because the bar display makes it easy to see whether the box is full of process memory or full of cache:

htop

If you want a quick refresher on interpreting process lists and htop output, the Linux process management guide is worth keeping nearby.

For pressure over time, vmstat is more useful than people give it credit for:

vmstat 1

I care about these columns most:

  • si and so — swap in and swap out activity
  • r — runnable tasks waiting for CPU
  • b — tasks blocked, often on I/O
  • wa — CPU time waiting on I/O
  • free, buff, cache — rough state of memory reclaim

If si and so stay at zero, small swap usage by itself usually does not bother me. If swap I/O is constant and latency is rising, that is a different story.

Why used memory does not mean exhausted memory

Linux uses memory for page cache because disk is slow and RAM is fast. If you read the same binaries, libraries, or data repeatedly, the kernel keeps hot pages in memory so it can serve them without going back to disk. That is why “used memory” frequently looks high on a healthy server.

I see this most clearly on web servers, package mirrors, and database hosts with regular file access patterns. After a reboot, memory might look mostly free. A few hours later, cache grows. That is expected. When applications need memory, the kernel can reclaim many of those cache pages.

A better mental model is this:

  • anonymous memory belongs to processes and may be harder to reclaim quickly
  • page cache is often reclaimable
  • dirty pages still need to be written back
  • swap usage is not automatically a problem

To inspect cache-heavy systems in more detail:

grep -E '^(Cached|Buffers|SReclaimable|Shmem|Dirty|Writeback):' /proc/meminfo

If Cached and SReclaimable are large while MemAvailable stays healthy, I usually leave the system alone. People sometimes try to “clear cache” to make the numbers look nicer, but that is usually counterproductive. Linux will repopulate the cache as soon as you hit the same workload again.

You can technically drop cache like this:

sudo sync
printf '3\n' | sudo tee /proc/sys/vm/drop_caches

I treat that as a troubleshooting tool, not routine tuning. It is useful if I want to measure cold-cache behavior, but I do not use it as a performance fix. On production servers it often just creates more disk I/O and worse response times.

If you are already looking at cache behavior because storage latency seems involved, pair your memory checks with the Linux disk I/O monitoring guide and the disk space troubleshooting. Memory pressure and storage pressure often show up together.

Reading memory pressure instead of guessing

A server can have nonzero swap, high cache, and still be perfectly healthy. What actually changes my response is pressure. On modern kernels, Pressure Stall Information is one of the best signals.

cat /proc/pressure/memory

Example:

some avg10=0.00 avg60=0.12 avg300=0.08 total=1234567
full avg10=0.00 avg60=0.01 avg300=0.00 total=34567

If those averages remain near zero, memory contention is usually light. If they keep climbing during load and users feel it, I know the kernel is spending real time waiting on reclaim rather than just managing cache normally.

I also like sar for historical trends if sysstat is installed:

sar -r 1 10
sar -B 1 10

On Debian or Ubuntu:

sudo apt update
sudo apt install sysstat

On RHEL-based systems:

sudo dnf install sysstat

sar -B is especially useful when I want to see page scans and paging behavior rather than a single static snapshot.

How the OOM killer really shows up in production

When the kernel cannot satisfy memory demands and reclaim is failing, it may invoke the OOM killer. The idea is simple: choose one or more processes to kill so the system survives. In practice, the choice can feel brutal because the process it selects may be your database, JVM, or application worker.

The first thing I do after a suspected OOM event is inspect the kernel log:

dmesg -T | grep -i -E 'out of memory|oom|killed process'

On systems using journalctl, this is often clearer:

journalctl -k -g 'oom\|Out of memory\|Killed process'

A real OOM sequence usually includes:

  • allocation failure messages
  • memory stats at the time of failure
  • the chosen victim process and PID
  • the process score or badness criteria

Processes can influence their OOM priority with /proc/<pid>/oom_score_adj. Lower values make a process less likely to be killed, higher values make it more likely.

To inspect a running process:

cat /proc/1234/oom_score
cat /proc/1234/oom_score_adj

I reserve custom oom_score_adj settings for cases where the blast radius is well understood. Protecting the wrong daemon can cause the kernel to kill everything around it instead.

panic_on_oom and why I rarely enable it

Linux exposes this knob:

cat /proc/sys/vm/panic_on_oom

You can change it temporarily with:

sudo sysctl -w vm.panic_on_oom=1

Values matter:

  • 0 — do not panic; use the OOM killer normally
  • 1 — panic on some OOM conditions
  • 2 — panic on all OOM conditions

I almost never enable this on ordinary application servers. A rebooting server is not inherently safer than a server that kills one process and stays reachable. I consider panic_on_oom only in tightly managed cluster environments where fencing, automatic replacement, or node eviction is part of the design. On a standalone server, turning an OOM event into an immediate kernel panic is often worse operationally.

The broader memory tuning context around swappiness, overcommit, and related knobs is covered in the Linux sysctl performance tuning guide, but the short version is that I treat panic-on-OOM as a specialized setting, not a general best practice.

Swap basics: partition or file?

Both swap partitions and swap files are valid. Years ago I still preferred partitions for simplicity and compatibility, but on modern Linux I almost always use swap files unless there is a specific reason not to.

Why I prefer swap files now:

  • easy to size after deployment
  • easy to add on cloud VMs
  • no repartitioning downtime
  • easy to remove later

A dedicated swap partition still makes sense if you are designing a system image from scratch, using hibernation, or following a strict storage layout. If I already use LVM, adding a swap LV can also be clean:

sudo lvcreate -L 8G -n swap vg0
sudo mkswap /dev/vg0/swap
sudo swapon /dev/vg0/swap

If you work with logical volumes a lot, the LVM guide covers the storage side in more detail.

For most servers, though, a swap file is quicker and good enough.

Creating a swap file the modern way

I use fallocate first, then fall back to dd only if the filesystem does not support it correctly.

sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile

Verify it:

swapon --show
free -h

Make it persistent across reboots by adding this line to /etc/fstab:

/swapfile none swap sw 0 0

On filesystems where fallocate is not suitable, use dd instead:

sudo dd if=/dev/zero of=/swapfile bs=1M count=4096 status=progress
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile

A common mistake is forgetting the chmod 600 step. mkswap will warn you, and it should. A world-readable swap file is a security problem because swapped pages may contain sensitive data.

If you ever need to remove it cleanly:

sudo swapoff /swapfile
sudo sed -i '\|^/swapfile none swap sw 0 0$|d' /etc/fstab
sudo rm -f /swapfile

How much swap I actually provision

There is no universal formula anymore. The old “swap equals 2x RAM” rule is one of those habits that survived longer than it should have. On a 256 GB server, blindly following it can be absurd.

What I usually do instead:

  • small VM with 1–4 GB RAM: 1–4 GB swap, mostly as a safety net
  • general-purpose app server with 8–32 GB RAM: 2–8 GB swap
  • memory-critical database host: case by case, often small swap plus tight monitoring
  • hibernation use case: enough swap to support hibernation requirements

I like having some swap even on well-sized servers. Not because I want active swapping, but because a little breathing room is better than immediate OOM on brief spikes. A small swap area can keep the kernel calm during bursts, especially if cold anonymous pages can move out.

Where I get cautious is assuming swap is a substitute for RAM. It is not. If the working set is larger than physical memory for sustained periods, swap just converts a memory problem into a latency problem.

vm.swappiness and vfs_cache_pressure

The default vm.swappiness on many distributions is still 60:

sysctl vm.swappiness

Temporarily change it like this:

sudo sysctl -w vm.swappiness=10

Persist it with a drop-in file:

printf 'vm.swappiness = 10\n' | sudo tee /etc/sysctl.d/99-memory-tuning.conf
sudo sysctl --system

For many servers, I prefer 10. That does not disable swap. It tells the kernel to avoid swapping anonymous pages too eagerly while still keeping swap available as a safety valve.

I avoid going straight to 0 unless I have tested the workload carefully. On some kernels, 0 can still swap under real pressure, but it changes reclaim behavior in ways people do not always expect. In practice, 10 is a sensible starting point for web and application servers.

vfs_cache_pressure affects how aggressively the kernel reclaims directory and inode caches relative to page cache.

sysctl vm.vfs_cache_pressure
sudo sysctl -w vm.vfs_cache_pressure=50

A lower value tells the kernel to hold onto VFS cache longer. I touch this less often than swappiness, but on file-heavy workloads it can matter. I treat it as workload-specific tuning, not a default tweak to paste everywhere.

Is swap bad for SSDs?

This question comes up constantly. In practical terms, ordinary swap usage is not what worries me most on modern SSD-backed servers. Sustained thrashing is bad for performance first and device wear second.

If a machine is swapping heavily all day, the bigger issue is that it is undersized or misconfigured. The storage write pattern is just one symptom. Modern SSD endurance is usually good enough that a few hundred megabytes or a few gigabytes of occasional swapping is not the main risk.

What I avoid is designing systems that depend on constant swap churn. That causes latency spikes, noisy-neighbor effects on shared storage, and ugly tail performance. If vmstat 1 shows steady si and so values under load, I solve the workload issue before I worry about theoretical wear.

On thin cloud volumes or busy network-attached storage, swap can feel even worse because the backing device is slower and more variable than local NVMe. In those cases, swap remains useful as a safety net, but I keep it modest.

Finding out what is actually in swap

Sometimes I want to know whether swap contains old idle pages from a few background processes or whether something important is actively getting paged out.

A quick built-in method is:

for pid in /proc/[0-9]*; do
  awk '/VmSwap/ {print FILENAME ": " $2 " kB"}' "$pid"/status 2>/dev/null
 done | sort -t: -k2 -n

That shows per-process swap usage from /proc/<pid>/status. It is rough but useful.

For more detail, smem is handy. On Debian or Ubuntu:

sudo apt update
sudo apt install smem
smem -rs swap

On RHEL-based systems, package availability varies by repository, so I often fall back to /proc parsing if smem is not already available.

If I need deep inspection, I look at smaps:

grep -E '^(Name|Pid|VmSwap|Rss|Pss)' /proc/1234/status /proc/1234/smaps_rollup 2>/dev/null

That helps distinguish a server with a little cold background memory in swap from a server that is actively starving a latency-sensitive process.

Huge pages, transparent huge pages, and databases

Huge pages can reduce TLB overhead for memory-intensive workloads, especially databases. This is one of those areas where broad advice is dangerous because the right answer depends heavily on the application.

Transparent Huge Pages, or THP, try to automate part of this. On some general-purpose workloads that is fine. On some databases, especially older or highly tuned deployments, THP can introduce latency spikes due to defragmentation and allocation behavior.

Check THP state:

cat /sys/kernel/mm/transparent_hugepage/enabled
cat /sys/kernel/mm/transparent_hugepage/defrag

On a database host where the vendor recommends disabling THP, I do that explicitly and use application-specific huge page guidance instead. That is different from enabling explicit huge pages for software that knows how to use them.

The common mistake is mixing those concepts. “Use huge pages” and “leave THP enabled” are not the same instruction.

Memory overcommit and why it matters before OOM does

Linux overcommit behavior decides how willing the kernel is to promise memory before it is actually needed. The relevant setting is vm.overcommit_memory:

sysctl vm.overcommit_memory

Values are:

  • 0 — heuristic overcommit handling
  • 1 — always overcommit
  • 2 — do not overcommit beyond the calculated limit

I leave many general-purpose servers at 0. For certain databases and memory-sensitive applications, I follow the application vendor’s recommendation carefully because allocation expectations differ.

To see the current commit situation:

grep -E 'CommitLimit|Committed_AS' /proc/meminfo

If Committed_AS is far beyond available backing and your workload forks or allocates aggressively, you are closer to OOM territory than free -h alone might suggest.

Common mistakes I keep seeing

A short list of operational mistakes that come up over and over:

  1. Treating page cache as wasted RAM. It usually is not.
  2. Disabling swap entirely on every server. That removes a useful safety valve.
  3. Using massive swap to hide chronic RAM shortage. That just moves the pain.
  4. Changing swappiness without checking actual swap activity. The metric matters more than the superstition.
  5. Ignoring kernel logs after a crash. OOM evidence is often right there.
  6. Tuning memory in isolation. CPU wait, disk latency, and process behavior matter too.
  7. Forgetting security on swap files. Always lock permissions down.

A practical monitoring baseline

If I am responsible for a Linux server long term, I want these signals somewhere central:

  • MemAvailable
  • swap used and swap I/O rate
  • PSI memory pressure from /proc/pressure/memory
  • major page fault rate
  • OOM kill events from kernel logs
  • top processes by RSS and swap usage

For spot checks, these are still my core commands:

free -h
vmstat 1
cat /proc/pressure/memory
dmesg -T | grep -i -E 'oom|out of memory|killed process'
swapon --show

Those five commands tell me enough to decide whether I am looking at harmless cache growth, mild reclaim, active swapping, or a server heading toward an incident.

Conclusion

Good Linux memory management is mostly about interpretation before tuning. I do not start by changing knobs. I start by deciding whether the system is actually under pressure, whether swap is behaving like a safety net or like a crutch, and whether the kernel logs show reclaim trouble or OOM history.

My default posture is simple: keep some swap, set vm.swappiness conservatively on servers, watch PSI and swap I/O rather than headline “used memory,” and tune only after I understand the workload. That approach has saved me from a lot of unnecessary firefighting.

If you want to continue down the tuning path, the natural next step is the Linux sysctl performance tuning guide, because many of the memory decisions that matter live there.

Scroll to Top