Why free -h is misleading and what actually matters
Anyone who sees free -h for the first time often mistakes their system for being short on memory, because buff/cache appears as used. This article explains how the page cache puts idle RAM to work for performance, why swap is an overflow mechanism rather than primary memory, and how to read proc meminfo for an accurate picture of the real memory state.
Table of Contents
- 1. Why free -h looks alarming at first
- 2. The page cache: putting idle RAM to good use
- 3. /proc/meminfo: the data source behind free
- 4. Swap: overflow storage, not primary memory
- 5. Getting swappiness and kernel parameters right
- 6. The OOM killer: when it strikes and how to steer it
- 7. Analyzing memory usage per process
- 8. cgroups and memory limits for containers and services
- 9. Monitoring tools compared
- 10. Summary
- 11. FAQ
1. Why free -h looks alarming at first
Anyone running free -h on a production server for the first time often gets a shock: the used column shows a high value, and there seems to be almost no free memory left. That impression is misleading, because in the classic view free does not account for the fact that a large portion of the supposedly occupied memory can be released at any moment. The Linux kernel follows the philosophy that unused memory is wasted memory, and consistently fills free RAM with data from the filesystem cache as long as no process needs it more urgently.
The column that actually matters is not used, but available. It estimates how much memory is truly available for new applications without triggering swapping, taking reclaimable portions of the page cache into account. A server with 32 GB of RAM, 18 GB used, and 13 GB available is nowhere near its limit; it is simply using its memory efficiently. Anyone who only looks at used and panics into clearing caches or upgrading RAM is solving a problem that does not exist, while giving up real performance benefits of the caching mechanism.
# Typical free -h output on a server with 32 GB RAM
$ free -h
total used free shared buff/cache available
Mem: 31Gi 18Gi 412Mi 1.2Gi 13Gi 13Gi
Swap: 2.0Gi 128Mi 1.9Gi
# "used" looks high and "free" looks almost empty, but "available" tells the real story
# available accounts for reclaimable buff/cache, not just literally unused pages
# Show raw kibibytes instead of human-readable values for scripting
$ free -k --wide
2. The page cache: putting idle RAM to good use
The page cache stores recently read or written file contents in RAM so that repeated accesses do not have to hit the disk or SSD again. For a Magento store with frequently read configuration files, session data, or database files, this translates into noticeably shorter access times, because the kernel transparently keeps these blocks around in the background. Applications themselves do not need to configure anything for this; the cache operates entirely transparently at the kernel level and grows automatically as long as free memory is available.
As soon as a process requests additional memory, the kernel releases cache pages without delay, prioritizing frequently used pages over rarely read ones. This mechanism explains why buff/cache in free -h keeps growing as system uptime increases, until almost all free memory appears occupied, even though the system is functioning perfectly normally. Manually clearing the cache via drop_caches is almost never a good idea in production, since it temporarily hurts performance because repeated disk access becomes necessary until the cache rebuilds itself.
3. /proc/meminfo: the data source behind free
/proc/meminfo provides the raw numbers from which free, vmstat, and most monitoring tools calculate their values, and it is worth understanding for anyone who wants to truly grasp memory issues. The fields MemTotal, MemFree, and MemAvailable form the foundation: MemFree shows completely unused memory, while MemAvailable provides a realistic estimate of how much memory is available for new applications once reclaimable caches are taken into account. Since kernel 3.14, MemAvailable has been the most reliable single metric for capacity planning and alerting.
Other important fields include Cached for the page cache, Buffers for block device metadata buffers, Dirty for changes not yet written to disk, and SwapFree for unused swap space. The Slab and SReclaimable fields show kernel-internal memory used for data structures such as dentries and inodes, part of which can also be released under memory pressure. Anyone diagnosing memory issues should always look at several of these fields together instead of relying on a single number.
# Read the raw counters behind free and vmstat
$ cat /proc/meminfo | head -20
MemTotal: 32860192 kB
MemFree: 432108 kB
MemAvailable: 13584220 kB
Buffers: 210344 kB
Cached: 12980112 kB
SwapCached: 2048 kB
SwapTotal: 2097148 kB
SwapFree: 1966080 kB
Dirty: 8420 kB
Writeback: 0 kB
Slab: 892340 kB
SReclaimable: 612880 kB
SUnreclaim: 279460 kB
# MemAvailable already accounts for reclaimable Cached + SReclaimable
# Compare MemFree (raw, tiny) with MemAvailable (realistic, much larger)
4. Swap: overflow storage, not primary memory
On modern Linux systems, swap is not a substitute for primary memory but a safety net for rare load spikes and rarely touched memory pages. The kernel preferentially swaps out pages that have not been touched in a long time, so that faster RAM stays free for actively used data. Moderate swap usage of a few hundred megabytes on a system with plenty of RAM is therefore completely normal and not a warning sign, as long as the swap activity itself, measured by si and so in vmstat, stays close to zero.
Swap only becomes a problem when actively used pages are continuously swapped out and back in, a state known as thrashing, which can dramatically degrade system performance because disk access is orders of magnitude slower than RAM access. On servers with SSD-backed swap, the effect is less dramatic than with spinning disks, but still noticeable. The si (swap in) and so (swap out) metrics from vmstat 1 show in real time whether swapping is actively occurring, while the plain swap-used value from free only shows the current amount swapped out, not the activity.
5. Getting swappiness and kernel parameters right
The kernel parameter vm.swappiness controls how aggressively the kernel swaps out memory pages, with a range of 0 to 200 on newer kernel versions, classically 0 to 100. A high value like 60, the default on many distributions, causes the kernel to start swapping out application memory in favor of additional page cache even under moderate memory pressure. For database servers and application servers with plenty of RAM, a lower value between 1 and 10 is usually more sensible, since it only allows swapping once memory is genuinely scarce.
A value of 0 does not fully disable proactive swapping; depending on the kernel version, it only swaps as a last resort to avoid a crash from memory exhaustion. Changes via sysctl vm.swappiness=10 take effect immediately but are lost on reboot, which is why persistent changes belong in a file under /etc/sysctl.d/. Besides swappiness, vm.vfs_cache_pressure, which controls how aggressively dentry and inode caches are reclaimed, as well as vm.dirty_ratio and vm.dirty_background_ratio, which govern writeback behavior for modified cache pages, also shape memory behavior under load.
; /etc/sysctl.d/99-memory-tuning.conf
; Lower swappiness for a database or application server with ample RAM
vm.swappiness = 10
; Reduce eagerness to reclaim dentry and inode caches under memory pressure
vm.vfs_cache_pressure = 50
; Start background writeback earlier to avoid large I/O bursts
vm.dirty_background_ratio = 5
vm.dirty_ratio = 10
; Apply immediately without a reboot:
; sysctl -p /etc/sysctl.d/99-memory-tuning.conf
6. The OOM killer: when it strikes and how to steer it
The out-of-memory killer only steps in once the kernel can no longer satisfy a memory request, neither from free RAM nor by swapping, and then selects a process for termination to keep the system from grinding to a complete halt. The selection is based on the so-called badness score, calculated from memory usage, runtime, and a manually adjustable oom_score_adj value. Processes with high memory usage and short runtime tend to be terminated first, while long-running system processes tend to be spared.
For critical services like a database, the risk of being hit by the OOM killer can be significantly reduced by writing -1000 to /proc/pid/oom_score_adj, while unimportant helper processes can be deliberately sacrificed first with a positive value. After an OOM event, dmesg or journalctl -k show exactly which process was killed and how much memory was in use at the time of the decision. Anyone who sees frequent OOM kills should not only investigate the affected process but also the system's overall memory balance, since another process is usually the original cause of the memory pressure.
7. Analyzing memory usage per process
To find out which process actually consumes how much memory, ps or top alone are often not enough, because by default they show the RSS value, which counts shared memory multiple times when several processes share a library. The tool smem instead calculates the PSS value, which proportionally distributes shared memory across all processes using it, giving a more realistic picture of actual memory load, especially for PHP-FPM workers or several concurrently running Node.js processes with shared modules.
For detailed analysis of a single process, cat /proc/pid/status provides fields such as VmRSS for the portion actually resident in RAM, VmSwap for that same process's swapped-out memory, and VmPeak for its historical peak. pmap -x pid additionally breaks memory down by individual memory mappings and shows whether a particular shared library or heap segment holds an unusually large amount of memory. Together, these tools allow a precise diagnosis, instead of relying on the often-misleading RSS column from top.
8. cgroups and memory limits for containers and services
Control groups, or cgroups for short, make it possible to enforce memory limits per process group independently of the system-wide memory state, and form the technical foundation for memory limits in Docker containers and systemd services. A container with a limit of 512 MB can never exceed that value, even if plenty of free memory remains on the host, because the kernel hard-enforces the limit at the cgroup level and triggers the cgroup's own OOM killer on breach, independently of the system-wide OOM killer.
Under systemd, comparable limits can be set directly in a service unit via MemoryMax and MemoryHigh, where MemoryHigh acts as a soft limit that throttles the process through increased reclaim, while MemoryMax acts as a hard limit that terminates the process on breach. For Magento or PHP-FPM deployments in containers, it is essential to set the memory limit realistically based on actually measured peak usage, since a limit set too tightly triggers the container's own OOM killer long before the host itself experiences memory pressure.
# docker-compose.yml: hard memory limit enforced via cgroups
services:
php-fpm:
image: mironsoft/php-fpm:8.4
deploy:
resources:
limits:
memory: 1024m
reservations:
memory: 512m
environment:
PHP_MEMORY_LIMIT: 768M
mysql:
image: mysql:8.0
mem_limit: 4096m
mem_reservation: 2048m
# Exceeding mem_limit triggers the cgroup-local OOM killer, not the host OOM killer
9. Monitoring tools compared
For ongoing monitoring, no single tool is sufficient on its own: free -h is good for a quick overview, vmstat 1 for observing swap activity in real time, and /proc/meminfo for precise analysis of individual fields in scripts and monitoring agents. Prometheus with the node_exporter automatically exports the relevant fields from /proc/meminfo as metrics, so that MemAvailable, SwapFree, and Cached can be visualized as a time series in Grafana and equipped with alerting thresholds, instead of relying on manual spot checks.
The following overview shows where misinterpretations typically occur and how the metrics should actually be read.
| Metric | Common Misreading | Correct Meaning | Recommendation |
|---|---|---|---|
| used (free -h) | Memory is almost full | Includes reclaimable page cache | Check the available column instead |
| buff/cache | Wasted memory | Instantly reclaimable performance cache | Only react when available drops |
| Swap used > 0 | System is thrashing, urgent | Kernel swapped out cold pages | Check si/so in vmstat, not used alone |
| MemAvailable | Unimportant extra line | Most realistic available memory figure | Use it for alerting thresholds |
| High process RAM usage | OOM killer strikes immediately | Kills only when an allocation cannot be served | Set oom_score_adj for critical services |
In practice, it pays to look at several metrics together: a decline in MemAvailable over several days combined with rising swap usage points to a memory leak in an application, while a single short spike is usually harmless. Understanding these relationships avoids both unnecessary panic over normal cache growth and overlooked real memory problems that only become visible through the combination of several metrics.
{
"alert": "LowMemoryAvailable",
"expr": "node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes < 0.10",
"for": "10m",
"labels": { "severity": "warning" },
"annotations": {
"summary": "Available memory below 10 percent for 10 minutes",
"description": "MemAvailable, not MemFree, is the metric that reflects real memory pressure."
}
}
Mironsoft
Server performance, kernel tuning and monitoring for Magento infrastructure
Ready to diagnose memory issues on your server reliably?
We analyze memory behavior, swap configuration, and cgroup limits across your Magento and PHP infrastructure, pinpoint real bottlenecks, and set up monitoring that tells normal cache growth apart from genuine problems.
Memory Audit
free, /proc/meminfo and vmstat analysis to pinpoint real bottlenecks
Swap & Kernel Tuning
Tune swappiness, dirty_ratio and OOM score to match your workload
Monitoring Setup
Prometheus/Grafana dashboards with meaningful alerting thresholds
10. Summary
Linux memory management follows a simple but often misunderstood principle: unused memory is wasted memory. The page cache automatically fills free RAM with frequently used file contents and releases it without delay as soon as applications need it. free -h therefore rarely shows much genuinely free memory even when nothing is wrong, as long as the available column stays comfortably large. /proc/meminfo, with MemAvailable, SwapFree, and the cache fields, provides the raw data for a precise assessment that goes beyond the surface of free.
Swap is an overflow mechanism for rarely used memory pages, not primary storage, and moderate swap usage without active si/so values in vmstat is harmless. vm.swappiness, the OOM killer with oom_score_adj, and cgroup-based memory limits give administrators targeted levers to steer memory behavior under load predictably, instead of relying on chance or blanket cache clearing. Continuous monitoring with several metrics considered together reliably surfaces real memory problems long before they cause outages.
Linux Memory Management: RAM, Swap, Cache - The Essentials at a Glance
Reading free -h correctly
Look at available, not used. buff/cache is reclaimable, not occupied memory.
Page Cache
Not wasted memory, but a performance booster. Released instantly on demand.
Swap & Swappiness
An overflow mechanism. Check si/so in vmstat, not just the swap-used value.
OOM Killer & cgroups
oom_score_adj and MemoryMax for controlled behavior under memory pressure.