This covers setting up hibernation on CachyOS with GNOME, GRUB, btrfs, full-disk encryption (LUKS unlocked at the GRUB prompt), and zram already in use. Verified on a Lenovo ThinkBook with GNOME 49.
Written for CachyOS, but most of this should apply to other Arch-based distributions. A few caveats: CachyOS ships with a systemd-based initrd and zram enabled by default — if your distribution uses a busybox-based initrd (i.e. base and udev hooks rather than systemd), you will need a resume hook in mkinitcpio instead of relying on systemd’s native resume handling. The efivarfs sleep hook in Step 5 is firmware-specific and not distribution-specific. Distributions that use GRUB with LUKS and btrfs in the same way as described here should be able to follow this guide without modification.
There are steps taken in this guide concerning efivars, that are very specific for the laptop it was written for (Lenovo Thinkpad T14 Gen 4)
Take a backup before proceeding. Editing bootloader configuration and the initramfs can leave the system unbootable if something goes wrong. At minimum, know how to boot from a live USB and chroot into your installation to roll back changes.
A few things that differ from what you might find on the Arch wiki:
- The Arch wiki says to skip
resume=kernel parameters when using systemd hooks. This is correct in principle, but certain Lenovo (and possibly other) machines panic on resume due to a bug in EFI runtime services triggered byefivarfs. The fix is a sleep hook that unmountsefivarfsbefore hibernation and remounts it after. Theresume=andresume_offset=parameters are still worth adding as a reliable fallback. - zram coexists fine — no need to disable it or do anything special.
- No changes to mkinitcpio
HOOKSare needed. The systemd-based initrd handles resume natively.
Step 1: Create the btrfs swapfile
CachyOS uses zram for swap by default — there is no swap partition. You need a dedicated swapfile for hibernation. It must live in its own btrfs subvolume to avoid COW, compression, and snapshot problems.
Size rule: the swapfile must be at least RAM + zram combined. A swapfile that is too small causes silent failure — the machine boots fresh with no error.
Check your RAM and zram sizes first:
$ free -h | awk '/^Mem/ {print $2}'
$ swapon --show
Create a dedicated subvolume:
$ sudo btrfs subvolume create /swap
Create the swapfile (replace 64g with your size):
$ sudo btrfs filesystem mkswapfile --size 64g --uuid clear /swap/swapfile
Use btrfs filesystem mkswapfile rather than fallocate — on btrfs, fallocate does not disable COW or compression, both of which must be disabled along with checksums.
Verify the file is a single extent (required):
$ sudo filefrag /swap/swapfile
# Must report: 1 extent — if not, recreate the file
Step 2: Activate swap and add to fstab
$ sudo swapon /swap/swapfile
$ swapon --show # both /swap/swapfile and /dev/zram0 should appear
zram stays as primary swap at priority 100. The swapfile at priority 0 only fills during hibernation — no performance impact during normal use.
Add to /etc/fstab:
/swap/swapfile none swap defaults,pri=0 0 0
Step 3: Get UUID and resume_offset
The kernel needs the UUID of the btrfs filesystem and the physical offset of the swapfile. On btrfs you must use the btrfs-specific tool — filefrag gives virtual addresses and produces a wrong offset.
UUID of the filesystem holding the swapfile:
$ sudo findmnt -no UUID -T /swap/swapfile
With FDE via GRUB this is the UUID of the decrypted btrfs volume (e.g. dm-0), not the LUKS container UUID. They are different — always use the output of this command, not values from lsblk for the LUKS partition.
Physical offset of the swapfile:
$ sudo btrfs inspect-internal map-swapfile -r /swap/swapfile
Note both values down carefully. A single wrong character in either causes silent resume failure — the machine boots fresh with no error.
Step 4: Add kernel parameters to GRUB
Edit /etc/default/grub and append resume= and resume_offset= to GRUB_CMDLINE_LINUX_DEFAULT:
GRUB_CMDLINE_LINUX_DEFAULT="nowatchdog quiet splash loglevel=3 \
resume=UUID=YOUR-BTRFS-UUID \
resume_offset=YOUR-OFFSET"
Always use the UUID= form for resume= — device paths like /dev/dm-0 are not stable across reboots.
Regenerate grub.cfg:
$ sudo grub-mkconfig -o /boot/grub/grub.cfg
Verify the parameters are present:
$ grep resume /boot/grub/grub.cfg | head -3
Step 5: Add efivarfs sleep hook
Certain firmware implementations (confirmed on Lenovo, possibly others) have a bug where efivarfs triggers a page fault inside EFI runtime services during resume, causing a kernel panic. The fix is to unmount efivarfs before hibernation and remount it after resume.
This approach preserves full EFI variable functionality — unlike efi=noruntime — so suspend-then-hibernate works correctly.
Create /usr/lib/systemd/system-sleep/efivarfs-hibernate.sh:
#!/bin/bash
case "$1/$2" in
pre/hibernate|pre/hybrid-sleep|pre/suspend-then-hibernate)
umount /sys/firmware/efi/efivars
;;
post/hibernate|post/hybrid-sleep|post/suspend-then-hibernate)
mount -t efivarfs efivarfs /sys/firmware/efi/efivars
;;
esac
$ sudo chmod +x /usr/lib/systemd/system-sleep/efivarfs-hibernate.sh
If you still get a kernel panic after adding the hook, fall back to adding efi=noruntime to GRUB_CMDLINE_LINUX_DEFAULT and regenerating grub.cfg. Note that efi=noruntime disables EFI variable writes for the entire session, which breaks suspend-then-hibernate’s automatic transition.
Step 6: Regenerate initramfs
No changes to mkinitcpio.conf are needed. Rebuild to keep things consistent after the config changes:
$ sudo mkinitcpio -P
Your HOOKS should look like this — leave them untouched:
HOOKS=(base systemd autodetect microcode kms modconf block keyboard sd-vconsole plymouth sd-encrypt filesystems)
If you have a resume hook from a previous attempt, remove it — it is redundant with systemd hooks and can cause ordering conflicts.
Step 7: Configure sleep modes
By default systemd only allows sleep modes that are explicitly enabled. Uncomment the relevant lines in /etc/systemd/sleep.conf:
[Sleep]
AllowHibernation=yes
AllowSuspendThenHibernate=yes
AllowHybridSleep=yes
# Optional: delay before suspend-then-hibernate transitions to disk:
HibernateDelaySec=7200 # 2 hours — adjust to taste
GNOME extensions read these settings. If a sleep mode is not allowed here it will not appear in the power menu even if everything else is configured correctly.
Step 8: Expose hibernate in GNOME
GNOME hides hibernate and related options by default. Two things are needed: a polkit rule and a GNOME Shell extension.
Create /etc/polkit-1/rules.d/85-hibernate.rules:
polkit.addRule(function(action, subject) {
if (action.id == "org.freedesktop.login1.hibernate" ||
action.id == "org.freedesktop.login1.hibernate-multiple-sessions" ||
action.id == "org.freedesktop.login1.handle-hibernate-key" ||
action.id == "org.freedesktop.login1.hibernate-ignore-inhibit")
{
if (subject.isInGroup("users")) {
return polkit.Result.YES;
}
}
});
For the GNOME Shell extension, check your shell version first: gnome-shell --version
- Power off options — supports GNOME 45–49+, exposes hibernate, hybrid sleep, and suspend-then-hibernate. Recommended.
- Hibernate Status Button — supports GNOME 45–48, hibernate only.
Install gnome-browser-connector if needed (sudo pacman -S gnome-browser-connector) and then install the extension from the GNOME Extensions website.
Step 9: Reboot and test
Clear any stale hibernation image before the first test:
$ sudo swapoff /swap/swapfile && sudo swapon /swap/swapfile
$ sudo reboot
After reboot, verify swap:
$ swapon --show
# Expected: /swap/swapfile (pri=0) and /dev/zram0 (pri=100)
Test hibernation:
$ sudo systemctl hibernate
The machine writes its image to disk and powers off. On power-on: LUKS passphrase → GRUB → session restored.
Verify in the kernel log after resume:
$ dmesg | grep -i 'resume\|hibernate\|swsusp'
What does NOT need to be done
- No
resumehook in mkinitcpio - No changes to
HOOKSat all - No dedicated swap partition
- No disabling or stopping zram; zram coexists fine at
pri=100
Troubleshooting
Kernel panic on resume — CapsLock blinking
The call trace showed efi_call_rts or _pfx_efi_call_rts and a page fault. To see the full trace: at the GRUB menu press e, remove quiet and splash, set loglevel=7.
Primary fix: the efivarfs sleep hook from Step 5. Verify it is executable: ls -la /usr/lib/systemd/system-sleep/efivarfs-hibernate.sh
Fallback: add efi=noruntime to GRUB_CMDLINE_LINUX_DEFAULT and regenerate grub.cfg. A BIOS update does not resolve this on affected Lenovo hardware.
Machine boots fresh instead of resuming
- Wrong UUID or offset — re-run both commands from Step 3 and compare verbatim to
/etc/default/grub. Verify withgrep resume /boot/grub/grub.cfg. - grub.cfg not regenerated — always run
sudo grub-mkconfig -o /boot/grub/grub.cfgafter editing/etc/default/grub. - Stale image — if you changed parameters between hibernations, run
sudo swapoff /swap/swapfile && sudo swapon /swap/swapfilebefore testing again. - Swapfile too small — check
swapon --show. If the RAM image exceeded swapfile capacity the kernel aborts silently. Checkdmesg | grep -i 'swap\|hibern'. - Multiple extents —
sudo filefrag /swap/swapfilemust say 1 extent. If not, recreate the swapfile.
Hibernate option missing from GNOME power menu
sleep.conf—AllowHibernation=yes(and others as needed) must be uncommented.- Extension version — Hibernate Status Button does not work with GNOME 49+. Use Power off options instead.
- Polkit rule — verify the file exists at
/etc/polkit-1/rules.d/85-hibernate.rules. Checkloginctl show-session | grep CanHibernate— should returnyes. - Swap not detected —
cat /sys/power/state— the worddiskmust be present. If absent, swap is not active or is too small.
systemctl hibernate returns error or permission denied
Without sudo: check the polkit rule from Step 8 is in place. Verify: loginctl show-session | grep CanHibernate.
With sudo and still failing: cat /sys/power/state — disk must be present.
Journal: journalctl -b -1 | grep -iE 'hibern|swsusp|resume'
Black screen or corrupted display after resume
NVIDIA: enable preservation services and add the memory allocation parameter:
$ sudo systemctl enable nvidia-suspend.service nvidia-hibernate.service nvidia-resume.service
Add nvidia.NVreg_PreserveVideoMemoryAllocations=1 to GRUB_CMDLINE_LINUX_DEFAULT and regenerate grub.cfg.
Intel/AMD: check dmesg | grep -i 'drm\|i915\|amdgpu' after resume. Common workarounds: i915.enable_psr=0 (Intel), amdgpu.vm_update_mode=3 (AMD).
Sources
- How to enable hibernate (UEFI, LUKS, btrfs, systemd-initcpio) using a swapfile — CachyOS forums
- Power management/Suspend and hibernate — Arch wiki
- Setting up a btrfs-compatible swap file with hibernation on CachyOS
- Enable hibernation in CachyOS alongside zram swap with full-disk encryption on btrfs — meganerd.nl