Every “is my VPS out of memory?” decision rests on two numbers from /proc/meminfo, and most operators read the wrong one. MemFree tells you how much RAM is completely unused — which on a healthy Linux host is always small, because the kernel spends spare memory on page cache. MemAvailable is the kernel’s own estimate of how much can be reclaimed under pressure without swapping. This article builds a repeatable budget: what the kernel costs, what your services cost in RSS and PSS, and how to prove the accounting adds up.
Why MemFree Is the Wrong Number
grep -E 'MemTotal|MemFree|MemAvailable|Cached|Buffers|SReclaimable|Shmem|Slab' /proc/meminfo
# MemAvailable is already computed by the kernel at mm/vmscan.c level.
# Reproduce it intentionally to understand the formula:
awk '/MemFree/{f=$2} /Cached/{c=$2} /SReclaimable/{s=$2}
/Shmem/{sh=$2} END{printf "approx available: %.0f MB\n", (f + c + s - sh)/1024}' /proc/meminfo
On a 1 GB VPS, MemFree of 60 MB with Cached of 500 MB is a well-utilised host, not a struggling one. Alert on MemAvailable / MemTotal < 15% sustained, combined with swap-in activity from vmstat:
# the only line that matters for memory pressure
vmstat 5 12 # columns: si (swap in) so (swap out)
# per-cgroup pressure stall information — best early-warning signal
cat /sys/fs/cgroup/system.slice/memory.pressure
# some avg10=0.00 avg60=0.05 avg300=0.10 total=182340
# full avg10=0.00 avg60=0.02 avg300=0.05 total=91211
Building the Overhead Budget
RSS double-counts shared pages. PSS divides each shared page by the number of mappers, which is the only honest way to attribute memory to services. Use PSS for budgeting:
# per-process PSS, memory in KB, largest first
sudo smem -tk --sort=pss | head -20
# or without smem, from /proc directly
for p in $(ls /proc | grep -E '^[0-9]+$'); do
pss=$(awk '/^Pss:/{s+=$2} END{print s+0}' /proc/$p/smaps_rollup 2>/dev/null)
[ -n "$pss" ] && [ "$pss" -gt 0 ] && \
printf "%8d KB %s\n" "$pss" "$(tr -d '\0' /dev/null)"
done | sort -rn | head -20
Now account for the parts that never show up under a process name:
| Item | Typical cost on a 1 GB VPS | Where to see it |
|---|---|---|
| Kernel slab | 80–200 MB | Slab in /proc/meminfo, slabtop |
| Page tables | 20–60 MB | PageTables |
| Kernel stack + task structs | ~16 KB × threads | KernelStack, thread count |
tmpfs / /dev/shm | Often unbounded | Shmem, df -h /dev/shm |
| Socket / conntrack buffers | 5–40 MB at high connection counts | tcp_mem, nf_conntrack |
| Hugepages | 2 MB per reserved page | HugePages_Total |
| Fragmentation reserve | ~5% unusable in practice | indirect |
# find the slab hogs
sudo slabtop -o -s c | head -15
# page table cost scales with mapped memory, not with process count alone
grep -E 'PageTables|KernelStack|Shmem|Slab|SUnreclaim' /proc/meminfo
# thread count is a memory cost multiplier
ps -eLf | wc -l
# tmpfs can silently eat gigabytes
df -h | grep -E 'tmpfs|shm'
Practical Budget on a 1 GB VPS
Starting from 1024 MB of MemTotal, a realistic allocation that avoids OOM without wasting RAM:
MemTotal 1024 MB
kernel + slab 160 MB (fixed tax)
page tables 30 MB
nginx (PSS) 45 MB
php-fpm 4 workers 180 MB (4 x 45 MB PSS, shared opcache counted once)
MariaDB 280 MB (innodb_buffer_pool 128M + per-thread overhead)
redis 25 MB
systemd/journald 40 MB
sshd + cron + misc 35 MB
-----------------------------
allocated 795 MB
headroom 229 MB (page cache + burst, target >=15%)
If your accounting shows less than 15% headroom, either reduce a line item or accept swap as a safety net rather than a strategy. A methodical reduction of the largest consumers — buffer pools and worker counts — is covered in depth in the memory-focused tuning guides on virtualserversvps.com. For workloads that genuinely need more than 1 GB, re-examine whether a larger plan is cheaper than engineering around a hard ceiling: the plan comparison on virtualserversvps.com lays out the break-even point.
Verify the Accounting Adds Up
# 1. sum PSS across all processes
total_pss=$(for p in /proc/[0-9]*; do
awk '/^Pss:/{s+=$2} END{print s+0}' "$p/smaps_rollup" 2>/dev/null; done \
| paste -sd+ | bc)
echo "total PSS: $((total_pss/1024)) MB"
# 2. kernel-side consumption
awk '/MemTotal/{t=$2} /MemFree/{f=$2} /Cached/{c=$2} /Slab/{s=$2}
END{printf "used-non-cache: %.0f MB\n", (t-f-c)/1024}' /proc/meminfo
# 3. the two should reconcile within ~5%; a large gap means kernel
# allocations you are not accounting for (usually slab fragmentation)
sudo slabtop -o -s c | head -5
Guardrails
- Alert on
MemAvailable, never onMemFree. - Budget services by PSS from
smaps_rollup, and treat the thread count as a multiplier. - Audit
Shmemandtmpfsmonthly; they grow silently. - Set explicit cgroup
memory.highon each service so one component cannot trigger a host-wide OOM kill. - Validate with a reconciliation script and store the output — a budget you never check is a guess.
Memory planning on a small VPS is arithmetic, not intuition. Once MemAvailable and PSS-based accounting are both in place, “why did my server get OOM-killed” becomes a solved query rather than a mystery.

Leave a Reply
You must be logged in to post a comment.