Linux uses almost all free RAM as page cache, swaps rarely used pages to disk and, when memory runs out completely, kills a process through the OOM killer. The defaults are designed for a wide range of machines, so a server with a clear workload can usually do better. In this tutorial you will learn to read memory usage correctly, add a swap file, tune vm.swappiness and related settings, enable compressed swap with zswap and make sure the OOM killer spares your most important services on Ubuntu 24.04.
Prerequisites
- A server running Ubuntu 24.04 LTS, for example a CubePath VPS. Debian 12 behaves the same way.
- A non-root user with
sudoprivileges. - About 2 GB of free disk space for the swap file.
Step 1 - Reading memory usage correctly
Start with free:
free -h
total used free shared buff/cache available
Mem: 3.8Gi 1.2Gi 214Mi 12Mi 2.6Gi 2.6Gi
Swap: 0B 0B 0B
A low free value is normal: the kernel keeps file data in buff/cache and releases it when applications need it. The number that matters is available, which estimates how much memory can be given to new processes without swapping. Here 2.6 GiB are available even though only 214 MiB are "free".
To see whether the system is actually short of memory, check Pressure Stall Information (PSI):
cat /proc/pressure/memory
some avg10=0.00 avg60=0.00 avg300=0.00 total=0
full avg10=0.00 avg60=0.00 avg300=0.00 total=0
some is the percentage of time at least one task was stalled waiting for memory, full the percentage of time all tasks were stalled. Sustained values above a few percent mean the server needs more RAM or less memory usage.
Finally, vmstat shows swap activity in real time. Watch the si (swap in) and so (swap out) columns:
vmstat 2 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 0 219140 71432 2641200 0 0 3 18 152 260 2 1 97 0 0
Occasional swap out is fine. Constant non-zero si and so means the server is thrashing.
To list the processes using the most memory:
ps -eo pid,user,rss,comm --sort=-rss | head -n 10
RSS is the resident memory in KiB, the RAM the process actually occupies.
Step 2 - Adding a swap file
Ubuntu cloud images, including most VPS templates, ship without swap. A modest swap area gives the kernel somewhere to move idle pages and turns a sudden OOM kill into a gradual slowdown you can notice and fix. For servers, 1 to 2 GB, or up to the size of RAM on small instances, is enough.
Check that no swap exists yet:
sudo swapon --show
If the command prints nothing, create a 2 GB file:
sudo fallocate -l 2G /swapfile
Restrict it to root, format it as swap and enable it:
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
Verify:
sudo swapon --show
NAME TYPE SIZE USED PRIO
/swapfile file 2G 0B -2
Make it permanent by adding it to /etc/fstab. First back up the file:
sudo cp /etc/fstab /etc/fstab.bak
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
Check the file for errors before the next reboot:
sudo findmnt --verify
NoteOn Btrfs, a swap file needs special handling (
btrfs filesystem mkswapfile). The steps above are for ext4 and XFS, the defaults on Ubuntu and Rocky Linux.
Step 3 - Tuning swappiness and cache behavior
Kernel memory settings are changed with sysctl. Check the current values first:
sysctl vm.swappiness vm.vfs_cache_pressure vm.dirty_ratio vm.dirty_background_ratio
vm.swappiness = 60
vm.vfs_cache_pressure = 100
vm.dirty_ratio = 20
vm.dirty_background_ratio = 10
What each one does:
vm.swappiness(0 to 200, default60): how willing the kernel is to swap out anonymous memory instead of dropping page cache. Lower values keep application memory in RAM longer.10is a common choice for database and application servers. Avoid0, which makes the kernel swap only under severe pressure and leads to sudden OOM kills.vm.vfs_cache_pressure(default100): how aggressively the kernel reclaims directory and inode caches. A value of50keeps them longer, which helps file servers with many small files.vm.dirty_background_ratioandvm.dirty_ratio: the percentage of memory that can hold modified data before the kernel starts writing it in the background, and before writing processes are forced to wait. On servers with a lot of RAM, lowering them (for example5and10) avoids long write bursts that stall I/O.
Create a configuration file so the values persist across reboots:
sudo nano /etc/sysctl.d/99-memory-tuning.conf
vm.swappiness = 10
vm.vfs_cache_pressure = 50
vm.dirty_background_ratio = 5
vm.dirty_ratio = 10
Load it without rebooting:
sudo sysctl --system
Confirm the new values:
sysctl vm.swappiness vm.vfs_cache_pressure
vm.swappiness = 10
vm.vfs_cache_pressure = 50
Change one setting at a time and watch vmstat and PSI for a few days before moving on. These values are starting points, not universal answers.
Step 4 - Enabling zswap compressed swap
zswap keeps pages that are about to be swapped out in a compressed pool in RAM. Many pages compress 2 to 3 times, so the server can swap more without touching the disk, at the cost of some CPU. It still needs a regular swap device (the file from Step 2) as a backing store.
Check whether it is enabled and which compressor it uses:
grep -r . /sys/module/zswap/parameters/
/sys/module/zswap/parameters/enabled:N
/sys/module/zswap/parameters/compressor:lzo
/sys/module/zswap/parameters/max_pool_percent:20
...
Enable it with the zstd compressor through the kernel command line so it is active from boot:
sudo nano /etc/default/grub
Append the parameters to the existing GRUB_CMDLINE_LINUX_DEFAULT line, keeping what is already there:
GRUB_CMDLINE_LINUX_DEFAULT="... zswap.enabled=1 zswap.compressor=zstd"
Some cloud images override this line in a file under /etc/default/grub.d/. If one exists, add the parameters there instead. Then regenerate the GRUB configuration and reboot:
sudo update-grub
sudo reboot
After the reboot, confirm zswap is active:
cat /sys/module/zswap/parameters/enabled /sys/module/zswap/parameters/compressor
Y
zstd
Once the server starts swapping, you can see how many pages are stored compressed:
sudo grep -E 'Zswap|Zswapped' /proc/meminfo
Step 5 - Protecting services from the OOM killer
When memory and swap are exhausted, the kernel kills the process with the highest OOM score, which is usually the one using the most memory. On a database server that is often the database itself. Each process has an adjustment from -1000 (never kill) to 1000 (kill first).
First check whether OOM kills have happened:
sudo journalctl -k | grep -i -E 'out of memory|oom-kill'
Sep 20 03:12:44 web01 kernel: Out of memory: Killed process 2143 (mysqld) total-vm:3145728kB, anon-rss:1843200kB ...
View the current score of a process, for example MySQL:
cat /proc/$(pidof mysqld)/oom_score
The right way to change it permanently is a systemd drop-in, because a value written to /proc is lost when the service restarts. Create one for the service you want to protect:
sudo systemctl edit mysql
Add the following between the comment lines the editor shows:
[Service]
OOMScoreAdjust=-800
Save, then restart the service and verify:
sudo systemctl restart mysql
cat /proc/$(pidof mysqld)/oom_score_adj
-800
You can do the opposite for disposable workloads, for example OOMScoreAdjust=500 on a batch worker, so it is killed before anything critical. You can also cap a service's memory with MemoryMax= in the same drop-in, so a leak in one service cannot starve the rest:
[Service]
MemoryMax=1G
Avoid -1000 except for truly essential processes: if nothing can be killed, the whole server can hang.
Step 6 - Transparent huge pages for databases
Transparent Huge Pages (THP) back memory with 2 MB pages to reduce TLB misses. Ubuntu 24.04 sets THP to madvise, meaning only applications that request it get huge pages:
cat /sys/kernel/mm/transparent_hugepage/enabled
always [madvise] never
madvise is a safe default. Some software, such as Redis, warns in its logs when THP is set to always because it causes latency spikes. If your database vendor recommends never, add transparent_hugepage=never to the kernel command line as in Step 4.
Troubleshooting
fallocate failed: Operation not supported: the file system does not supportfallocate. Create the file withsudo dd if=/dev/zero of=/swapfile bs=1M count=2048instead.swapon: /swapfile: insecure permissions 0644: runsudo chmod 600 /swapfile.- The server swaps constantly even with low swappiness: the workload does not fit in RAM. Find the top consumers with
ps(Step 1), lower application memory limits, or resize the server. - zswap is still
Nafter reboot: check that the parameters are in/proc/cmdline. If not, they were added to a GRUB file that is overridden by/etc/default/grub.d/.
Conclusion
You can now tell real memory pressure apart from normal cache usage, and your server has a swap file, tuned swappiness and cache settings, compressed swap and OOM priorities that protect the services that matter. As next steps, monitor PSI and swap activity over time with your monitoring stack, and review the I/O scheduler of your disks so that swapping, when it happens, has as little impact as possible.
