On many modern laptops (especially AMD-based systems from Dell, Lenovo, HP, and others), closing the lid suspends the laptop — but it still drains roughly 3% battery per hour, potentially dying overnight. This is caused by the suspend mode the hardware uses.
Modern AMD laptops and many recent laptops have dropped S3 sleep ("Suspend to RAM") in favour of Modern Standby (s2idle). You can confirm this:
cat /sys/power/mem_sleep
# Output: [s2idle] ← only s2idle listed; deep (S3) is not availableWith S3, the CPU and nearly all hardware cut power entirely during suspend. With s2idle, the CPU stays in a low-power idle loop — much higher drain.
This is a hardware/firmware limitation driven by Microsoft's Modern Standby standard. Many vendors have removed S3 from their ACPI tables entirely. There is often no BIOS setting to change it.
Configure systemd to:
- Suspend normally when the lid closes (fast, instant wake for short absences)
- Automatically hibernate after 60 minutes of suspended sleep (writes RAM to disk, powers off completely — zero battery drain)
When you open the lid after hibernation, your full session restores exactly as you left it.
| Field | Fedora | Ubuntu |
|---|---|---|
| Hardware | Dell Inspiron 15 3525 | Dell Inspiron 15 3525 |
| CPU | AMD Ryzen 7 5825U | AMD Ryzen 7 5825U |
| OS | Fedora 44 | Ubuntu 26.04 |
| Kernel | 6.19.9-300.fc44.x86_64 | 7.0.0-10-generic |
| systemd | 259 | 259 |
| Filesystem | Btrfs on NVMe | ext4 on NVMe |
| Available sleep states | freeze mem disk |
freeze mem disk |
The steps below apply to both distros. Distro-specific sections are clearly marked.
Hibernate requires writing your entire RAM to disk. Many systems use zram as their only swap device — this is compressed in-memory swap and cannot be used for hibernate. Check:
swapon --show
# If you only see /dev/zram0 (TYPE=partition, backed by RAM), you need a persistent swap fileYou need a swap file on persistent storage at least as large as your RAM. Check RAM size:
free -h | grep MemThe steps below create an 8 GB swap file — adjust to match your RAM in MB.
Btrfs — swap files must have copy-on-write disabled. Use dd (not fallocate — it doesn't work on Btrfs). The file must exist and be empty before setting chattr +C.
sudo touch /swapfile
sudo chattr +C /swapfile
# Fill with zeros — adjust count= to your RAM size in MB
sudo dd if=/dev/zero of=/swapfile bs=1M count=8192 status=progress
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
swapon --showext4 / XFS — use fallocate instead:
sudo fallocate -l 8G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
swapon --showOn SELinux-enabled systems, files created at / get a generic label. The swap file needs the swapfile_t type or systemd-logind will be denied access and hibernate will fail with "Access denied".
sudo semanage fcontext -a -t swapfile_t "/swapfile"
sudo restorecon -v /swapfile
# Verify
ls -laZ /swapfile
# Should show: unconfined_u:object_r:swapfile_t:s0Note: If you skip this step on a system with SELinux in Enforcing mode,
systemctl hibernatewill return "Access denied". Checksudo ausearch -m avc -ts recentif you hit unexplained access denials.
Ubuntu uses AppArmor (not SELinux) — skip this step entirely.
Add this line to /etc/fstab:
/swapfile none swap defaults 0 0
Both Fedora and Ubuntu 26.04+ use dracut to build the initrd. Without the resume dracut module, the initrd will not run systemd-hibernate-resume.service on boot, leaving /sys/power/resume as 0:0 — and hibernate will fail with "Invalid resume config".
Check if it's already included:
# Fedora
sudo lsinitrd /boot/initramfs-$(uname -r).img | grep resume
# Ubuntu
sudo lsinitramfs /boot/initrd.img-$(uname -r) | grep resume
# Look for: usr/lib/systemd/system/systemd-hibernate-resume.serviceIf not present, add it permanently and rebuild:
echo 'add_dracutmodules+=" resume "' | sudo tee /etc/dracut.conf.d/resume.conf
sudo dracut --forceThis config persists across kernel updates.
Verify it's now included:
# Fedora
sudo lsinitrd /boot/initramfs-$(uname -r).img | grep resume
# Ubuntu
sudo lsinitramfs /boot/initrd.img-$(uname -r) | grep resume
# Should now show systemd-hibernate-resume.servicesudo mkdir -p /etc/systemd/sleep.conf.dCreate /etc/systemd/sleep.conf.d/hibernate.conf:
[Sleep]
AllowSuspend=yes
AllowHibernation=yes
AllowSuspendThenHibernate=yes
HibernateDelaySec=60min
HibernateMode=platform shutdownHibernateDelaySec=60min— after 60 minutes of suspend, hibernate automatically. Adjust to taste (e.g.30min).HibernateMode=platform shutdown— uses ACPI platform method first, then powers off.
sudo mkdir -p /etc/systemd/logind.conf.dCreate /etc/systemd/logind.conf.d/lid.conf:
[Login]
HandleLidSwitch=suspend-then-hibernate
HandleLidSwitchExternalPower=suspend-then-hibernate
HandleLidSwitchDocked=ignoreNote:
HandleLidSwitchExternalPower=suspend-then-hibernatemeans the same behaviour applies on AC power. Change toignoreif you use your laptop docked with the lid closed.
On Ubuntu 26.04+, polkit does not correctly map processes running inside user app scopes (e.g. terminals launched from a Wayland compositor) to the active session — so implicit active: yes never triggers and systemctl hibernate returns "Access denied".
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.suspend-then-hibernate") {
if (subject.isInGroup("sudo")) {
return polkit.Result.YES;
}
}
});This allows any member of the sudo group to hibernate without a password prompt.
Fedora correctly maps active session processes — skip this step.
Hibernate needs the kernel to know where to find the saved RAM image on next boot. You need two values: the UUID of the device containing the swap file, and the physical offset of the swap file within that device.
Get the device UUID:
# Find which device your swap file is on
df /swapfile
# e.g. /dev/nvme0n1p2
# Get its UUID
lsblk -o NAME,UUID /dev/nvme0n1p2 # replace with your deviceGet the physical resume offset — method depends on filesystem:
# Btrfs — use btrfs inspect-internal (filefrag returns logical offset on Btrfs, which is wrong)
sudo btrfs inspect-internal map-swapfile -r /swapfile
# Example output: 12592384
# ext4 / XFS — use filefrag, take the physical_offset of the first extent
sudo filefrag -v /swapfile | head -6
# Look for the physical_offset in the first data row (ext 0), e.g.:
# 0: 0.. 0: 77576192.. 77576192: 1:
# The value is 77576192Do not use
filefragon Btrfs — it returns the logical offset, which differs from the physical offset the kernel needs and will silently break hibernate.
Set the kernel parameters:
Fedora — use grubby:
sudo grubby --update-kernel=ALL \
--args="resume=UUID=<your-uuid> resume_offset=<your-offset>"
# Verify
sudo grubby --info=DEFAULT | grep argsUbuntu — edit /etc/default/grub:
# Add resume=UUID=<your-uuid> resume_offset=<your-offset> to GRUB_CMDLINE_LINUX_DEFAULT
sudo nano /etc/default/grub
# e.g.: GRUB_CMDLINE_LINUX_DEFAULT="quiet splash resume=UUID=a1e3233e-... resume_offset=77576192"
sudo update-grubThe logind config, initrd changes, and kernel parameters all take effect after a reboot. Save all open work before rebooting.
sudo rebootRun without sudo — polkit handles privilege elevation:
systemctl hibernateThe system should power off. On next boot, your full session should restore. If it reboots cold (no session restore), check the kernel resume parameters match the output of the offset command above.
Temporarily lower the delay to 2 minutes for testing:
# Edit /etc/systemd/sleep.conf.d/hibernate.conf, set:
# HibernateDelaySec=2min
sudo systemctl daemon-reload
# Trigger manually
systemctl suspend-then-hibernate
# Wait 2+ minutes, then open the lid — should restore from hibernateRestore to 60min when confirmed working.
systemd-analyze cat-config systemd/sleep.conf
systemd-analyze cat-config systemd/logind.confcat /proc/cmdline | grep resumecat /sys/power/resume
# Should show a non-zero device number like 259:2, not 0:0
# If it shows 0:0, the dracut resume module isn't in the initrd — re-check Step 4cat /sys/class/power_supply/BAT0/capacity| Error | Cause | Fix |
|---|---|---|
Invalid resume config: resume= is not populated yet resume_offset= is |
/sys/power/resume is 0:0 — dracut resume module missing from initrd |
Add add_dracutmodules+=" resume " to /etc/dracut.conf.d/resume.conf and run sudo dracut --force, then reboot |
Access denied on Fedora |
SELinux denying systemd-logind access to the swap file |
Run sudo ausearch -m avc -ts recent; apply semanage fcontext and restorecon from Step 2 |
Access denied on Ubuntu |
polkit not mapping app-scoped processes to the active session | Create the polkit rule from Step 7 |
| Session restores but then freezes | Btrfs resume offset is wrong | Re-run sudo btrfs inspect-internal map-swapfile -r /swapfile and update resume_offset=, then reboot |
# Disable swap file
sudo swapoff /swapfile
# Remove from fstab (delete the /swapfile line)
sudo nano /etc/fstab
# Remove systemd drop-ins
sudo rm /etc/systemd/sleep.conf.d/hibernate.conf
sudo rm /etc/systemd/logind.conf.d/lid.conf
sudo systemctl restart systemd-logind
# Remove dracut config and rebuild initrd
sudo rm /etc/dracut.conf.d/resume.conf
sudo dracut --force
# Remove kernel parameters:
# Fedora
sudo grubby --update-kernel=ALL --remove-args="resume resume_offset"
# Ubuntu
sudo nano /etc/default/grub # remove resume= and resume_offset= from GRUB_CMDLINE_LINUX_DEFAULT
sudo update-grub
# Ubuntu only — remove polkit rule
sudo rm /etc/polkit-1/rules.d/85-hibernate.rules
# Fedora only — remove SELinux fcontext rule
sudo semanage fcontext -d "/swapfile"
# Reboot for kernel parameter changes to take effect
sudo reboot
# After reboot, delete the swap file
sudo rm /swapfile- Tested on Fedora 44 (systemd 259, kernel 6.19.9) and Ubuntu 26.04 (systemd 259, kernel 7.0.0).
- Ubuntu 26.04+ uses dracut to build its initrd under the hood, despite
update-initramfsstill being the frontend command. The dracut module approach works on both distros. - On Ubuntu,
lsinitramfsis the frontend for listing initrd contents; on Fedora, uselsinitrd. btrfs inspect-internal map-swapfile -rrequiresbtrfs-progs(installed by default on Fedora;sudo apt install btrfs-progson Debian/Ubuntu).- Do not use
filefragto get the swap offset on Btrfs — it returns the logical offset, which differs from the physical offset the kernel needs. Always usebtrfs inspect-internal map-swapfile -r. - If the swap file's physical offset changes (e.g. after a Btrfs balance or defragmentation), re-run the offset step and reboot.
- zram swap remains active alongside
/swapfileand continues to be used for normal paging (it's faster). systemd correctly selects the NVMe-backed swap file for hibernate. HandleLidSwitchExternalPower=suspend-then-hibernateensures the same behaviour on AC — the laptop will hibernate after 60 minutes even when plugged in. Change tosuspendif you prefer it to stay suspended indefinitely on AC.- Run
systemctl hibernatewithoutsudo. Usingsudochanges the session/process context and can cause "Access denied" even with polkit correctly configured.