Most people who think their VPS is "out of RAM" are looking at the wrong number. Linux fills spare memory with disk cache on purpose, so a low "free" figure is normal. The number that matters is available, and the events that matter are swapping and the OOM killer. This guide walks through the commands that show all three, then a simple way to log memory for a day so an upgrade decision is based on data rather than one alarming screenshot.
Everything below works on current Ubuntu, Debian and AlmaLinux. Commands that need root are shown with sudo.
Step 1: read free -h properly
free -h Illustrative output on a 4 GB VPS running WordPress and MySQL:
total used free shared buff/cache available Mem: 3.8Gi 1.9Gi 210Mi 60Mi 2.0Gi 1.9Gi Swap: 2.0Gi 12Mi 2.0Gi What each column means:
- total: RAM the kernel can use. It is a little under the plan size because the kernel reserves some for itself.
- used: memory that is not available to new programs. How it is calculated depends on the version of
free(see the note below). - free: memory doing nothing at all. On a healthy server this is usually small, and that is fine.
- buff/cache: the page cache (file contents read from disk) plus buffers and reclaimable kernel caches.
- available: the kernel's estimate of how much memory a new program could get without swapping. It counts free memory plus the part of the cache that can be dropped.
Read the available column. In the example above, 210 MB is "free" but 1.9 GB is available. The server is not short of memory.
A note on used: newer versions of free (Ubuntu 24.04, Debian 12) calculate it simply as total minus available, which is what the example shows, so used + free + buff/cache can add up to more than total. On AlmaLinux 9, used is total minus free minus buff/cache, so the same server would show a smaller figure there. Either way, available is the column to read.
Add -w (free -hw) to split buffers and cache into separate columns, or use free -h -s 5 to refresh every five seconds while you load-test.
Why the page cache is not "used up" RAM
When a program reads a file, Linux keeps a copy of it in RAM, and the next read comes from memory instead of the disk. Unused RAM does nothing for you, so the kernel lets the cache grow into whatever is spare. When a process asks for memory, the kernel reclaims clean cached pages and hands them over, usually quickly.
So a server showing 2 GB of buff/cache is not 2 GB short; it is using 2 GB to make disk reads faster. Do not run echo 3 > /proc/sys/vm/drop_caches to "free" it. That only makes the next few minutes slower while the cache refills.
Two exceptions are worth knowing. shared (Shmem) memory, which includes tmpfs files and some database shared memory, is counted in buff/cache but cannot be dropped. available already leaves Shmem out, which is another reason to trust that column. Dirty pages (file data waiting to be written) count as available but have to be flushed to disk before they can be reused, so a server with a lot of Dirty memory frees it more slowly.
Step 2: look at /proc/meminfo for the detail
free reads its numbers from /proc/meminfo. To see the lines that matter:
grep -E '^(MemTotal|MemFree|MemAvailable|Buffers|Cached|Shmem|SReclaimable|Dirty|SwapTotal|SwapFree|Committed_AS):' /proc/meminfo The useful extras are SReclaimable (kernel cache that can be freed), Shmem (shared memory and tmpfs, not freeable) and Committed_AS (memory processes have asked for, which can exceed RAM because Linux allows overcommit). A Committed_AS above MemTotal is not an error by itself, but on a busy box it shows how bad things would get if every process used what it reserved.
Step 3: find which processes use the memory
Sort processes by resident memory (RSS, in KiB):
ps -eo pid,user,rss,comm --sort=-rss | head -n 15 To show MiB instead:
ps -eo rss,comm --sort=-rss | awk 'NR>1 {printf "%8.1f MiB %s\n", $1/1024, substr($0, index($0,$2))}' | head -n 15 In top, press Shift+M to sort by memory, or start it already sorted with top -o %MEM. htop does the same when you click the MEM% column.
RSS has one catch: shared libraries and shared memory are counted in full for every process that maps them, so twenty PHP-FPM workers can look much bigger in total than they are. PSS (proportional set size) splits shared pages between the processes that share them, so the numbers add up. The smem tool reports it:
# Ubuntu / Debian sudo apt install smem # AlmaLinux (smem comes from EPEL) sudo dnf install epel-release && sudo dnf install smem sudo smem -r -s pss -k | head -n 15 # processes, largest PSS first sudo smem -u -k # totals per user If the package is not available on your system, the kernel exposes PSS directly. This lists the 10 processes with the largest PSS (in kB), by PID:
sudo grep -H '^Pss:' /proc/[0-9]*/smaps_rollup 2>/dev/null | sort -k2 -n -r | head Look up a PID with ps -p <pid> -o comm=.
Step 4: see memory per service with systemd
On current distributions every service runs in its own cgroup, and systemd tracks its memory (page cache charged to it included):
sudo systemd-cgtop -m -m orders the list by memory. For one service, systemctl status mariadb shows a Memory: line covering every process the service started. Use your own unit name: mariadb or mysql; php8.3-fpm on Ubuntu 24.04, php8.2-fpm on Debian 12, php-fpm on AlmaLinux; nginx, apache2 or httpd. This is often the quickest way to answer "is it MySQL or PHP?" The figure includes file cache charged to the service, so treat it as an upper bound.
Step 5: check swap and vm.swappiness
swapon --show sysctl vm.swappiness Swap that is configured but lightly used is normal; Linux moves rarely touched pages out to make room for cache. What matters is whether pages move in and out continuously. Watch that with vmstat:
vmstat 5 Ignore the first line; it is an average since boot. Every later line covers the last 5 seconds. The si (swap in) and so (swap out) columns should be zero or close to it most of the time. Steady non-zero values mean the working set does not fit in RAM and the server is waiting on disk.
vm.swappiness (default 60) controls how readily the kernel swaps out process memory instead of dropping cache. On a database or web server many admins lower it to 10:
echo 'vm.swappiness=10' | sudo tee /etc/sysctl.d/99-swappiness.conf sudo sysctl --system This is tuning, not a fix. If si and so are busy, lowering swappiness will not create memory that is not there.
Step 6: look for OOM-killer events
When RAM and swap are both exhausted, the kernel's out-of-memory killer picks a process (mostly by memory size, so on a web server it is often MySQL) and kills it. The usual symptom is a site showing a database connection error until the service restarts.
Search the kernel log for the current boot:
sudo dmesg -T | grep -iE 'out of memory|oom-kill|killed process' dmesg only covers the current boot, and on a busy server older messages may already have scrolled out of the buffer. The journal goes further back when it is persistent (the case on any system where /var/log/journal exists). Match on the kernel transport rather than using -k, because -k limits the search to the current boot:
sudo journalctl _TRANSPORT=kernel --since "7 days ago" --grep 'Out of memory|oom-kill|Killed process' sudo journalctl -k -b -1 --grep 'Out of memory' # the previous boot only Some systems, notably Ubuntu Desktop 22.04 and later, also run systemd-oomd, which can kill a whole cgroup under sustained memory pressure before the kernel steps in. Server and cloud images usually do not run it. Check whether it is active with systemctl is-active systemd-oomd; if it is, search its log:
sudo journalctl -u systemd-oomd --since "7 days ago" One OOM event is a fact, not a guess. If you find any, you either need more RAM or need to cap something: the InnoDB buffer pool, PHP-FPM pm.max_children, or Redis maxmemory.
Step 7: log memory over a full day
A single free -h shows one moment. Backups, cron jobs and traffic peaks happen at other moments. Two simple ways to capture a whole day:
vmstat to a file (no extra packages): one sample every 5 minutes for 24 hours, with timestamps. The count is 289 because the first line is the since-boot average.
nohup vmstat -t 300 289 > ~/vmstat-$(date +%F).log 2>&1 & The next day, ignore the first line and look at the free, cache, si and so columns around your busiest hours. vmstat has no available column; treat free + cache as a rough upper bound, or use sar (below), which records kbavail. To log the available figure itself with no extra packages:
nohup sh -c 'while sleep 300; do date +%T; grep MemAvailable /proc/meminfo; done' > ~/memavail-$(date +%F).log 2>&1 & This loop runs until you stop it: pkill -f 'while sleep 300'.
sar from sysstat (keeps history automatically, every 10 minutes by default):
# Ubuntu / Debian sudo apt install sysstat sudo sed -i 's/^ENABLED="false"/ENABLED="true"/' /etc/default/sysstat sudo systemctl enable --now sysstat # AlmaLinux sudo dnf install sysstat sudo systemctl enable --now sysstat After a day:
sar -r # memory: kbavail, %memused, kbcached, kbcommit sar -S # swap usage sar -W # pages swapped in/out per second Read earlier days with -f, for example sar -r -f /var/log/sysstat/sa14 on Debian/Ubuntu or sar -r -f /var/log/sa/sa14 on AlmaLinux (the number is the day of the month).
Reading the result: upgrade, tune, or downsize?
Take the lowest available (or kbavail in sar) across the day, then add the swap activity and the OOM search.
- Available stays above roughly 25-30% of total, no swapping, no OOM events. You have enough RAM. If it never drops below half, you may be paying for memory you do not use.
- Available dips under 10-15% at peaks, with occasional small
si/so. You are at the edge. Find which service grows (Step 4), cap it if it is a cache you control, or plan the next size up. - Steady swapping, or any OOM kills. The working set does not fit. Add RAM. A 1-2 GB swap file is a cushion that turns a crash into a slowdown, not a replacement for memory.
- RAM is fine but load is high and pages are slow. Memory is not the problem. Check CPU, including the
st(steal) column invmstat, before buying more RAM.
For a starting point by stack, our guide on how much RAM a VPS needs for each workload lists typical figures for WordPress, WooCommerce, cPanel, Minecraft and build servers, and recommends leaving 25-30% headroom on top of what the stack uses.
If the numbers say you need more (or less), you should not have to rebuild the server. On our KVM VPS plans RAM is reserved rather than oversold. As the pricing page explains, you can resize in either direction mid-month: vCPU and RAM changes apply on the next boot, disk can grow without a reboot but cannot shrink, and the difference is prorated to the day. At the time of writing, KVM VPS plans start at ₹674/mo for 2 vCPU and 4 GB of RAM (see the pricing page).
If your logs show plenty of available memory but CPU steal or uneven run times, more RAM will not help. That is the case for a VDS with dedicated CPU cores, where physical cores are pinned to your instance instead of shared. (VDS plans are ordered by enquiry at the time of writing.)
Quick reference
| Question | Command |
|---|---|
| How much can I still use? | free -h (the available column) |
| What is using it? | ps -eo pid,rss,comm --sort=-rss, sudo smem -r -s pss -k |
| Which service? | sudo systemd-cgtop -m |
| Am I swapping? | vmstat 5 (si and so, ignore the first line) |
| Did anything get killed? | sudo journalctl _TRANSPORT=kernel --grep 'Out of memory' |
| What happened over the day? | sar -r, sar -W |
Measure for a day, read available rather than free, and treat swapping and OOM kills as the real signals. Most "do I need more RAM?" questions answer themselves once you have those numbers.
Disclosure: written by the team at Bigdomainhost, an Indian hosting provider. We sell the hosting linked above.

