TL;DR: The default CachyOS setup (ZRAM with no disk swap) caused constant Out Of Memory (OOM) crashes when my 16GB RAM filled up, as the system had nowhere to push compressed memory. Simply adding a swapfile to ZRAM isn’t the answer because it causes LRU inversion. Switching to ZSWAP with a physical BTRFS swapfile provides an intelligent, kernel-aware cache that solved the crashes and enabled hibernation.
I have been daily driving CachyOS to get the best out of both gaming and development workloads. By default, CachyOS ships with ZRAM enabled and no physical disk swap. For a while, this was fine. But recently, my system started randomly killing apps.
My Zen browser would suddenly close. Electron apps like Spotify and Caprine kept crashing. The GNOME daemon was constantly throwing Out Of Memory (OOM) warnings. I had 16GB of RAM and a 1TB NVMe SSD, but my workflow involves running Docker containers, tmux sessions, and keeping everything open for days to avoid context loss. That workflow was hitting a hard brick wall.
At first, I just bypassed the issue. I stuck to standard suspend because I was initially intimidated by Linux kernel configurations and hibernation wasn’t an option with pure ZRAM. But after reading an interesting breakdown by Chris Down, I realized why my system was choking.
Some distributions, like Fedora, are fantastic for coding and development out of the box, and they default to ZRAM specifically because they want to remain diskless. But I want the freedom to configure my system exactly how I need it to run, which is exactly why I stick to Arch-based distros like CachyOS. I needed a pagefile, I needed hibernation, and most importantly, I needed my machine to stop crashing when my memory filled up.
Here is why ZRAM was failing me, why combining ZRAM with a swap file is a bad idea, and how to migrate your CachyOS setup to ZSWAP.
The Problem: ZRAM with No Swapfile
The root of the issue is how CachyOS configures memory by default: it uses ZRAM and absolutely zero physical disk swap.
ZRAM acts as a compressed block device in your memory. It is incredibly fast. But because there is no backing swapfile, it has a hard limit.
When I was gaming or spinning up heavy development environments, the system would request a large chunk of memory. The kernel checks the RAM, sees that it is full, and checks the ZRAM, which is also full. Because there is no physical pagefile to overflow into, the kernel hits a dead end.
With nowhere to push the compressed memory pages, the Linux Out-Of-Memory (OOM) killer wakes up. Its job is to save the system, so it looks for the process consuming the most memory and immediately terminates it. If that process is the game you are playing or the IDE you are coding in, it crashes instantly.
Why not just keep ZRAM and add a swap file?
It is a common thought. A lot of Linux users try to fix these OOM errors by keeping ZRAM but adding a physical swap file. They set ZRAM to a high priority (like pri=100) and the disk swap to a lower priority (pri=10).
On paper, the logic makes sense: fill up the fast RAM first, then spill over to the disk. In reality, it causes a severe performance trap known as LRU (Least Recently Used) Inversion.
The kernel treats ZRAM as a physical block device. It fills it up sequentially. Imagine you boot up your PC, open a browser, and read an article. You leave that tab open in the background. The kernel decides you aren’t actively using it, so it swaps that old tab out into your fast ZRAM block.
Over the next few hours, ZRAM gets 100% full of this stale data.
Now, you switch to a heavy task. The kernel urgently needs to swap out an application you were using just five minutes ago (a “warm” page). Because ZRAM is completely full of old browser tabs, the kernel has no choice: it sends your recently used, active application straight to the slow physical disk swap.
The priorities are inverted. Your dead, useless data is hoarding the fast memory, while your active working set is being punished on the slow SSD. Your system will start lagging, stuttering, or lock up entirely.
(Note: ZRAM does have a “writeback” feature to push data to disk, but it is notoriously difficult to configure, not dynamic, and lacks deep integration with the kernel’s memory shrinker.)
Why ZSWAP is the Better Cache
ZSWAP solves this elegantly because it isn’t a fake hard drive. It is an active, inline compressed cache that sits directly in front of your actual physical disk swap.
Because it is integrated directly into the kernel’s core memory management subsystem, the kernel maintains full visibility of what is hot and what is cold. When your RAM gets full and a new “warm” application needs to be swapped, ZSWAP looks at its pool and says: “I am full, but I see an old browser tab from six hours ago. I am going to kick that old tab down to the physical SSD, and put this newer application into my fast compressed RAM.”
By strictly using ZSWAP with a backing swap file, you guarantee that your fastest swap tier is always reserved for the data you are most likely to need next.
The Migration Guide
I set this up on CachyOS running Linux 6.19 with BTRFS, Limine as the bootloader, and GNOME. This guide assumes you currently have the default CachyOS ZRAM setup with no existing swapfile.
If you are using a different bootloader like GRUB, you can check the official CachyOS Wiki ZRAM to ZSWAP guide for those specific steps.
Step 1: Disable ZRAM
CachyOS ships a udev rule that explicitly disables ZSWAP to prevent conflicts with ZRAM. We need to override it with an empty file.
sudo touch /etc/udev/rules.d/30-zram.rules
Step 2: Create a BTRFS Swapfile
Since I want hibernation support, the swapfile needs to be larger than my 16GB of RAM. A 20GB file covers the worst-case hibernation image while leaving headroom for ZSWAP overflow.
You can’t just use dd or fallocate on BTRFS because of Copy-On-Write (COW). You have to use the built-in BTRFS tools to ensure the file has the nocow flag.
# Create a dedicated BTRFS subvolume
sudo btrfs subvolume create /swap
# Create the swap file
sudo btrfs filesystem mkswapfile --size 20g --uuid clear /swap/swapfile
# Activate it
sudo swapon /swap/swapfile
# Make it persistent across reboots
echo "/swap/swapfile none swap defaults 0 0" | sudo tee -a /etc/fstab
Step 3: Get Resume Values for Hibernation
To wake up from hibernation, the kernel needs to know exactly where the memory image is on the disk. On BTRFS, you need the physical offset. Do not use filefrag for this. It reports logical offsets, and your system will silently fail to resume.
# Get the partition UUID
findmnt -no UUID -T /swap/swapfile
# Get the physical resume offset
sudo btrfs inspect-internal map-swapfile -r /swap/swapfile
Write down both the UUID and the resume offset number.
Step 4: Configure Limine and Kernel Parameters
On CachyOS, you configure kernel parameters in /etc/default/limine. Don’t touch /boot/limine.conf as it gets overwritten on updates.
Open /etc/default/limine and find the KERNEL_CMDLINE[default]+= line. Append the following parameters, replacing the UUID and offset with your own:
systemd.zram=0 zswap.enabled=1 zswap.shrinker_enabled=1 zswap.compressor=lz4 zswap.max_pool_percent=30 resume=UUID=YOUR-UUID-HERE resume_offset=YOUR-OFFSET-HERE
Step 5: Update Initramfs
The kernel needs the lz4 compression module and the resume hook loaded early in the boot process.
Edit /etc/mkinitcpio.conf:
- Find the
MODULES=()array and addlz4. - Find the
HOOKS=()array and addresumeafterfilesystems.
Rebuild the initramfs:
sudo limine-mkinitcpio
Step 6: GNOME Hibernation Config
GNOME intentionally hides the hibernate button from the power menu. Since different distros handle swap files differently, they don’t enable it by default.
First, tell systemd that hibernation is allowed:
sudo mkdir -p /etc/systemd/sleep.conf.d/
sudo nano /etc/systemd/sleep.conf.d/hibernate.conf
Add the following:
[Sleep]
AllowHibernation=yes
HibernateMode=platform shutdown
Restart logind: sudo systemctl restart systemd-logind.
Finally, to get the button in the UI, install the gnome-shell-extension-hibernate-status package from the AUR.
paru -S gnome-shell-extension-hibernate-status
Note: If the extension doesn’t load, you may need to manually add your current GNOME version to its metadata.json file in /usr/share/gnome-shell/extensions/.
Results
After rebooting, I confirmed ZSWAP was running and ZRAM was disabled:
cat /sys/module/zswap/parameters/enabled # Outputs: Y
lsmod | grep zram # Outputs nothing
More importantly, I can verify that my new 20GB swapfile is actually being used by the system as a backing store when the fast cache fills up:
swapon --show
# NAME TYPE SIZE USED PRIO
# /swap/swapfile file 20G 5.8G -1
Since the switch, my machine hasn’t crashed once. My Docker containers and 40+ browser tabs stay right where I left them, and when I am done for the day, the system hibernates perfectly to my NVMe drive.
If you are running a CachyOS rig with limited RAM and heavy multitasking needs, dumping ZRAM for ZSWAP is entirely worth the 15 minutes it takes to configure.
The Security Trade-off: Data Remanence
There is one primary security downside when moving from a pure, volatile RAM setup to one that utilizes a physical disk swap. In the cybersecurity world, this is known as swap leakage or data remanence.
When your ZSWAP pool reaches its limit, the kernel’s shrinker takes the oldest, coldest compressed memory pages and pushes them down to your /swap/swapfile on the SSD. The kernel does not know what that data is. It could be harmless game textures, or it could be your decrypted password manager vault, active session tokens, SSH keys, or private browser tabs.
Once that data hits the physical SSD, it persists even after you shut down the computer. Because you also configured hibernation, this risk is magnified: systemctl hibernate dumps a snapshot of your entire active RAM state directly to persistent storage.
The Unencrypted Drive Reality
Because this setup uses standard BTRFS without LUKS full-disk encryption, your 20GB swapfile is sitting there as accessible data. If someone were to steal your laptop, they could mount the BTRFS partition on another machine and use forensic tools to carve out plain-text passwords and encryption keys that were previously residing in memory.
The standard defense against this is Swap Encryption or Full Disk Encryption. However, I haven’t implemented that yet, primarily because of a pragmatic security mindset. According to the Ten Immutable Laws of Security , physical access completely changes the threat model.
If someone has physical access to my PC and can pull my NVMe drive, my swapfile is the least of my worries. They already have access to my entire filesystem. Unless you are defending against state-level actors or corporate espionage, the massive stability gains of ZSWAP far outweigh the localized risk of physical swap leakage.
Future: Virtual Swap (Kernel 6.20+)
If you are concerned about relying on a physical swapfile for this caching behavior, there is a patch series (v4, targeting the 6.19 base, currently under maintainer review) that will decouple ZSWAP from requiring a physical swap file entirely.
Once it lands downstream to CachyOS, ZSWAP will be able to size its pool dynamically without needing a backing swapfile. You won’t need to change anything. Your setup will keep working, and the intelligent kernel tiering remains. The only difference is the swap file becomes optional. You can remove it if you prefer a pure RAM setup, or keep it as a cold-data overflow tier and for hibernation (which will always require physical storage).