A VPS running at 95% memory utilization is not efficient — it is one traffic spike away from thrashing. Linux reports “available” memory separately from “free” memory precisely because page cache is reclaimable and application allocations are not. The number to watch is available memory, and the headroom you should keep depends on how fast your workload can grow.
| Workload type | Burst multiplier | Target headroom | On a 2 GB plan, keep free |
|---|---|---|---|
| Static site behind a CDN | 1.2× | 20% | 400 MB |
| WordPress with page cache | 1.5–2× | 35% | 700 MB |
| WordPress without cache | 2–3× | 40% | 800 MB |
| Custom app with uncached queries | 3×+ | 50% | 1 GB |
| Database server | 1.3× | 25% | 500 MB |
Read the Right Column
free -m shows a free column that is usually near zero on a healthy server, because Linux uses spare RAM for page cache. That is not a problem. The column that matters is available, which discounts reclaimable cache and estimates what a new application allocation could get without swapping.
free -m
# total used free shared buff/cache available
# Mem: 1987 842 91 8 1054 986
#
# 986 MB available, 1054 MB in reclaimable cache -> healthy.
# Look at it as a percentage
free -m | awk 'NR==2{printf "%.1f%% available\n", $7/$2*100}'
As a rule of thumb, treat under 15% available as a warning and under 8% as an active incident. Below 8%, the kernel starts reclaiming aggressively, then swapping, and swap on a budget plan is typically slow shared storage — latency jumps before you see an out-of-memory kill.
The Two Metrics That Predict Trouble
Absolute headroom tells you where you are. These two tell you where you are going:
- Swap in/out rate (
vmstatcolumnssi/so): any sustained non-zero value means the working set no longer fits. Intermittent single-digit KB pages are normal noise; sustained hundreds of KB/s is not. - Page-scan rate (
sar -Bpgscan): rising scans with flat application memory means the kernel is hunting for free pages. That is the last warning before swap.
vmstat 5 12 | awk 'NR>2{print "avail-ish used:",$4" si:",$7" so:",$8}'
# Which process is actually growing
ps -eo rss,comm --sort=-rss | head -8
# Per-process growth over 60s
for i in 1 2 3; do ps -eo rss,comm --sort=-rss | head -4; sleep 20; done
Where Headroom Disappears
| Cause | Symptom | Fix |
|---|---|---|
| PHP-FPM pool sized too large | Many idle workers, each holding 40–60 MB | Cut pm.max_children to fit measured concurrency |
| MySQL buffer pool too generous | Database RSS grows to the configured cap | Set innodb_buffer_pool_size to ~50% of RAM on a shared box |
| Docker image layer cache | Slow, steady growth after each deploy | Prune images on a schedule |
| Unbounded log buffers with journald | Growth after incidents | Set SystemMaxUse |
| Memory leak in an app worker | RSS climbs monotonically over days | Restart worker on a timer, then fix the leak |
If two or more of these stack up, no amount of tuning recovers the headroom. That is the point where the RAM tier itself is the problem. Our VPS plan comparison table shows what each successive RAM tier costs per month, which makes the “tune vs. upgrade” decision a straight arithmetic comparison rather than a guess.
Right-Sizing the Two Processes That Eat Headroom
On a typical budget VPS, PHP-FPM and MySQL account for most of the working set, and both ship with defaults that assume more RAM than you have. The failure mode is not a crash — it is a slow bleed of available memory as workers accumulate.
| Surface | Common default | Budget-safe setting | Headroom recovered |
|---|---|---|---|
PHP-FPM pm.max_children | 25 on a 2 GB box | 8–10 (measured concurrency) | 600–900 MB |
MySQL innodb_buffer_pool_size | 128 MB (too small) or 70% of RAM (too large) | 40–50% of installed RAM | 200–400 MB |
Redis maxmemory | unbounded | memory limit + allkeys-lru | prevents unbounded growth |
journald SystemMaxUse | 10% of filesystem | 200–400 MB | disk, plus predictable logs |
Note the asymmetry in the MySQL row. Setting the buffer pool too small causes steady disk reads and higher latency; setting it too large removes the headroom that the web tier needs during bursts. On a single 2 GB server running both, 512 MB is a reliable compromise. On a 4 GB server that also runs the web tier, 1.5–2 GB is comfortable.
# How much memory PHP-FPM is really holding
ps -eo rss,cmd | grep 'php-fpm: pool' | awk '{s+=$1; n++} END{printf "%d workers, %.0f MB total, %.0f MB each\n", n, s/1024, s/1024/n}'
# MySQL resident size vs configured pool
ps -o rss= -C mysqld | awk '{printf "mysqld RSS: %.0f MB\n", $1/1024}'
mysql -e "SHOW VARIABLES LIKE 'innodb_buffer_pool_size';"
Run this under peak load, not at 3 AM. If PHP-FPM workers total more than 40% of installed RAM, the pool is over-provisioned regardless of what setting is in the config file.
How to Set a Headroom Alarm
Alert on available memory, not on usage, and alert on the trend rather than the instantaneous value. A useful threshold set:
- Warning: available drops below 20% for more than 10 minutes
- Critical: available below 10%, or any sustained
soabove 100 KB/s - Trend: 7-day peak available memory declines more than 25% week over week
Free tooling covers all three; our list of free monitoring tools for budget VPS servers includes the ones that graph available memory without a paid agent.
When you do upgrade, buy the next tier up rather than the next tier that exactly fits — headroom is the product you are actually paying for. InterServer holds pricing flat across the term so the upgrade math stays stable: view InterServer VPS tiers. If you would rather find the exact threshold empirically, Vultr lets you resize hourly and watch the headroom curve move in real time.



