This guide adds two new options to the GRUB menu for safely launching live modes. You will get two ways to launch dom0 in RAM for protection against forensics:
- Qubes Overlay-Live Mode
- Qubes Zram-Live Mode
This guide also adds the Ram‑Wipe module to wipe memory after shutdown. This tool is used by Tails and Kicksecure / Whonix to wipe memory for protection against forensics.
You will see new entries after shutdown Qubes ![]()
This guide also creates an ultra‑hardened overlay: root read‑only, strong hardening overlay mount, strong kernel hardening inside overlay (kernel hardening from Secureblue).
This guide solves the old problems:
1 implement live boot by porting grub-live to Qubes - amnesia / non-persistent boot / anti-forensics
2 Wipe RAM on shutdown
Overlay‑Live Mode is very fast and maximally secure. Overlay is used in Tails and Kicksecure/Whonix.
Zram‑Live Mode starts slowly, uses more CPU power, but saves RAM dramatically (~ 2× compared to Overlay‑Live). It is perfect for devices with 16 GB of memory or for running large VMs (Standalone qube) in live mode.
Both live modes significantly increase dom0 security:
Root mount in read‑only mode,
All operations are performed in the ultra-hardened RAM-overlay,
RAM-wipe after shutdown.
This also works great for experiments in Qubes or for beginners who want to learn without fear of breaking anything - all changes disappear after a reboot. It will extend the lifespan of your SSD.
Don’t worry about installing these modes - doing so won’t affect the default Qubes boot!
I created this scenario to be as safe as possible and isolated from the default Qubes boot:
The new GRUB options are added to/etc/grub.d/40_custom(so it don’t modify your/etc/default/grub),
The new dracut live modules start only if additional kernel parameters are added in GRUB (these parameters aren’t present in the default boot).
Important: The size of the dom0 memory must exceed the amount of space used on the dom0 disk! Otherwise Zram-Mode won’t start and Overlay-Mode may freeze when launching multiple VMs. The size of dom0 changes after each update; kernel updates can significantly increase the dom0 size on disk, and live modes may stop working after a kernel update.
Keep an eye on the dom0 size on the disk when install appVM for live modes and remove old kernels to save disk space.
Simple script for automatically creating Dracut Modules and GRUB Custom files
Make a backup before you run script ![]()
(
Use this script (second comment) with additional kernel parameters bootscrub=1 init_on_free=1 for maximum paranoid protection against forensics, but it may reduce system performance by 5-15%! )
Default Script
You just need:
Save the script into txt, for example, with name live.sh in /home/user/
Make the file executable. Run in terminal sudo chmod +x live.sh or tick the box in File Properties → Permissions → Program:
Copy file to dom0. Run it in dom0 terminal qvm-run --pass-io <qube-name> 'cat /home/user/live.sh' > live.sh
Run it with sudo sudo ./live.sh
#!/bin/bash
# Qubes Dom0 Live Boot and RAM-Wipe
# ⚠️ Make backup before running! Run as root: sudo ./live.sh
set -e # Exit on any error
#BOOT_UUID
BOOT_UUID=$(findmnt -n -o UUID /boot 2>/dev/null || echo "AUTO_BOOT_NOT_FOUND")
if [ "$BOOT_UUID" = "AUTO_BOOT_NOT_FOUND" ]; then
BOOT_UUID=$(blkid -s UUID -o value -d $(findmnt -n -o SOURCE /boot 2>/dev/null))
fi
# LUKS_UUID
LUKS_DEVICE=$(blkid -t TYPE="crypto_LUKS" -o device 2>/dev/null | head -n1 || echo "")
if [ -n "$LUKS_DEVICE" ]; then
LUKS_UUID=$(sudo cryptsetup luksUUID "$LUKS_DEVICE" 2>/dev/null)
else
LUKS_UUID="AUTO_LUKS_NOT_FOUND"
fi
# Latest XEN_PATH
XEN_PATH=$(ls /boot/xen*.gz 2>/dev/null | sort -V | tail -1 | xargs basename 2>/dev/null || echo "/xen-4.19.4.gz")
# Latest kernel/initramfs
LATEST_KERNEL=$(ls /boot/vmlinuz-*qubes*.x86_64 2>/dev/null | grep -E 'qubes\.fc[0-9]+' | sort -V | tail -1 | xargs basename)
LATEST_INITRAMFS=$(echo "/initramfs-${LATEST_KERNEL#vmlinuz-}.img")
# Max memory dom0
system_total_mb=$(xl info | grep total_memory | awk '{print $3}')
if [ -n "$system_total_mb" ] && [ "$system_total_mb" -gt 0 ] 2>/dev/null; then
# 80% total_memory
DOM0_MAX_MB=$((system_total_mb * 80 / 100))
DOM0_MAX_GB=$((DOM0_MAX_MB / 1024))
DOM0_MAX_RAM="dom0_mem=max:${DOM0_MAX_MB}M"
DOM0_MAX_GBG="${DOM0_MAX_GB}G"
else
DOM0_MAX_RAM="dom0_mem=max:10240M"
DOM0_MAX_GB="10"
DOM0_MAX_GBG="10G"
fi
# qubes_dom0-root
Qubes_Root=$(findmnt -n -o SOURCE /)
echo "=== Qubes Dom0 Live Boot Setup ==="
# Disable Dom0 Swap:
sed -i '/[[:space:]]\+swap[[:space:]]\+/s/^/#/' "/etc/fstab"
swapoff -a
# Create Dracut directories
echo "Creating Dracut modules..."
mkdir -p /usr/lib/dracut/modules.d/90ramboot
mkdir -p /usr/lib/dracut/modules.d/90overlayfs-root
# Create 90ramboot/module-setup.sh
cat > /usr/lib/dracut/modules.d/90ramboot/module-setup.sh << 'EOF'
#!/usr/bin/bash
check() {
return 0
}
depends() {
return 0
}
install() {
inst_simple "$moddir/zram-mount.sh"
inst_hook cleanup 00 "$moddir/zram-mount.sh"
}
EOF
chmod 755 /usr/lib/dracut/modules.d/90ramboot/module-setup.sh
# Create 90overlayfs-root/module-setup.sh
cat > /usr/lib/dracut/modules.d/90overlayfs-root/module-setup.sh << 'EOF'
#!/bin/bash
check() {
[ -d /lib/modules/$kernel/kernel/fs/overlayfs ] || return 1
}
depends() {
return 0
}
installkernel() {
hostonly='' instmods overlay
}
install() {
inst_hook pre-pivot 10 "$moddir/overlay-mount.sh"
}
EOF
chmod 755 /usr/lib/dracut/modules.d/90overlayfs-root/module-setup.sh
# Create overlay-mount.sh
cat > /usr/lib/dracut/modules.d/90overlayfs-root/overlay-mount.sh << 'EOF'
#!/bin/sh
. /lib/dracut-lib.sh
if ! getargbool 0 rootovl ; then
return
fi
modprobe overlay
mount -o remount,nolock,noatime $NEWROOT
mkdir -p /live/image
mount --bind $NEWROOT /live/image
umount $NEWROOT
mkdir /cow
mount -n -t tmpfs -o mode=0755,size=100%,nr_inodes=500k,noexec,nodev,nosuid,noatime,nodiratime tmpfs /cow
mkdir /cow/work /cow/rw
mount -t overlay -o noatime,nodiratime,volatile,lowerdir=/live/image,upperdir=/cow/rw,workdir=/cow/work,default_permissions,relatime overlay $NEWROOT
mkdir -p $NEWROOT/live/cow
mkdir -p $NEWROOT/live/image
mount --bind /cow/rw $NEWROOT/live/cow
umount /cow
mount --bind /live/image $NEWROOT/live/image
umount /live/image
EOF
chmod 755 /usr/lib/dracut/modules.d/90overlayfs-root/overlay-mount.sh
# Create zram-mount.sh
cat > /usr/lib/dracut/modules.d/90ramboot/zram-mount.sh << EOF
#!/bin/sh
. /lib/dracut-lib.sh
if ! getargbool 0 rootzram ; then
return
fi
mkdir /mnt
umount /sysroot
mount -o ro $Qubes_Root /mnt
modprobe zram
echo $DOM0_MAX_GBG > /sys/block/zram0/disksize
/mnt/usr/sbin/mkfs.ext2 /dev/zram0
mount -o nodev,nosuid,noatime,nodiratime /dev/zram0 /sysroot
cp -a /mnt/* /sysroot
exit 0
EOF
chmod 755 /usr/lib/dracut/modules.d/90ramboot/zram-mount.sh
# Create ramboot dracut.conf
echo "Creating dracut configuration..."
cat > /etc/dracut.conf.d/ramboot.conf << 'EOF'
add_drivers+=" zram "
add_dracutmodules+=" ramboot "
EOF
echo "Creating dracut ram-wipe module..."
# Create module directory
mkdir /usr/lib/dracut/modules.d/40ram-wipe/
# Create module-setup.sh
cat > /usr/lib/dracut/modules.d/40ram-wipe/module-setup.sh << 'EOF'
#!/bin/bash
# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
# ex: ts=8 sw=4 sts=4 et filetype=sh
## Copyright (C) 2023 - 2025 ENCRYPTED SUPPORT LLC <adrelanos@whonix.org>
## See the file COPYING for copying conditions.
# called by dracut
check() {
require_binaries sync || return 1
require_binaries sleep || return 1
require_binaries dmsetup || return 1
return 0
}
# called by dracut
depends() {
return 0
}
# called by dracut
install() {
inst_simple "/usr/libexec/ram-wipe/ram-wipe-lib.sh" "/lib/ram-wipe-lib.sh"
inst_multiple sync
inst_multiple sleep
inst_multiple dmsetup
inst_hook shutdown 40 "$moddir/wipe-ram.sh"
inst_hook cleanup 80 "$moddir/wipe-ram-needshutdown.sh"
}
# called by dracut
installkernel() {
return 0
}
EOF
chmod +x /usr/lib/dracut/modules.d/40ram-wipe/module-setup.sh
# Create wipe-ram-needshutdown.sh
cat > /usr/lib/dracut/modules.d/40ram-wipe/wipe-ram-needshutdown.sh << 'EOF'
#!/bin/sh
## Copyright (C) 2023 - 2025 ENCRYPTED SUPPORT LLC <adrelanos@whonix.org>
## See the file COPYING for copying conditions.
type getarg >/dev/null 2>&1 || . /lib/dracut-lib.sh
. /lib/ram-wipe-lib.sh
ram_wipe_check_needshutdown() {
## 'local' is unavailable in 'sh'.
#local kernel_wiperam_setting
kernel_wiperam_setting="$(getarg wiperam)"
if [ "$kernel_wiperam_setting" = "skip" ]; then
force_echo "wipe-ram-needshutdown.sh: Skip, because wiperam=skip kernel parameter detected, OK."
return 0
fi
true "wipe-ram-needshutdown.sh: Calling dracut function need_shutdown to drop back into initramfs at shutdown, OK."
need_shutdown
return 0
}
ram_wipe_check_needshutdown
EOF
chmod +x /usr/lib/dracut/modules.d/40ram-wipe/wipe-ram-needshutdown.sh
# Create wipe-ram.sh
cat > /usr/lib/dracut/modules.d/40ram-wipe/wipe-ram.sh << 'EOF'
#!/bin/sh
## Copyright (C) 2023 - 2025 ENCRYPTED SUPPORT LLC <adrelanos@whonix.org>
## See the file COPYING for copying conditions.
## Credits:
## First version by @friedy10.
## https://github.com/friedy10/dracut/blob/master/modules.d/40sdmem/wipe.sh
## Use '.' and not 'source' in 'sh'.
. /lib/ram-wipe-lib.sh
drop_caches() {
sync
## https://gitlab.tails.boum.org/tails/tails/-/blob/master/config/chroot_local-includes/usr/local/lib/initramfs-pre-shutdown-hook
### Ensure any remaining disk cache is erased by Linux' memory poisoning
echo 3 > /proc/sys/vm/drop_caches
sync
}
ram_wipe() {
## 'local' is unavailable in 'sh'.
#local kernel_wiperam_setting dmsetup_actual_output dmsetup_expected_output
## getarg returns the last parameter only.
kernel_wiperam_setting="$(getarg wiperam)"
if [ "$kernel_wiperam_setting" = "skip" ]; then
force_echo "wipe-ram.sh: Skip, because wiperam=skip kernel parameter detected, OK."
return 0
fi
force_echo "wipe-ram.sh: RAM extraction attack defense... Starting RAM wipe pass during shutdown..."
drop_caches
force_echo "wipe-ram.sh: RAM wipe pass completed, OK."
## In theory might be better to check this beforehand, but the test is
## really fast.
force_echo "wipe-ram.sh: Checking if there are still mounted encrypted disks..."
## TODO: use 'timeout'?
dmsetup_actual_output="$(dmsetup ls --target crypt 2>&1)"
dmsetup_expected_output="No devices found"
if [ "$dmsetup_actual_output" = "$dmsetup_expected_output" ]; then
force_echo "wipe-ram.sh: Success, there are no more mounted encrypted disks, OK."
elif [ "$dmsetup_actual_output" = "" ]; then
force_echo "wipe-ram.sh: Success, there are no more mounted encrypted disks, OK."
else
## dracut should unmount the root encrypted disk cryptsetup luksClose during shutdown
## https://github.com/dracutdevs/dracut/issues/1888
force_echo "\\
wipe-ram.sh: There are still mounted encrypted disks! RAM wipe incomplete!
debugging information:
dmsetup_expected_output: '$dmsetup_expected_output'
dmsetup_actual_output: '$dmsetup_actual_output'"
## How else could the user be informed that something is wrong?
sleep 5
fi
}
ram_wipe
EOF
chmod +x /usr/lib/dracut/modules.d/40ram-wipe/wipe-ram.sh
# Create ram-wipe dracut.conf.d
cat > /usr/lib/dracut/dracut.conf.d/30-ram-wipe.conf << 'EOF'
add_dracutmodules+=" ram-wipe "
EOF
# Create ram-wipe-lib.sh
mkdir /usr/libexec/ram-wipe
cat > /usr/libexec/ram-wipe/ram-wipe-lib.sh << 'EOF'
#!/bin/sh
## Copyright (C) 2023 - 2025 ENCRYPTED SUPPORT LLC <adrelanos@whonix.org>
## See the file COPYING for copying conditions.
## Based on:
## /usr/lib/dracut/modules.d/99base/dracut-lib.sh
if [ -z "$DRACUT_SYSTEMD" ]; then
force_echo() {
echo "<28>dracut INFO: $*" > /dev/kmsg
echo "dracut INFO: $*" >&2
}
else
force_echo() {
echo "INFO: $*" >&2
}
fi
EOF
chmod +x /usr/libexec/ram-wipe/ram-wipe-lib.sh
# Update INITRAMFS
dracut --verbose --force
# Create GRUB custom
echo "Creating GRUB custom ..."
cat > /etc/grub.d/40_custom << EOF
#!/usr/bin/sh
exec tail -n +3 \$0
menuentry 'Qubes Overlay-Live Mode (latest kernel)' --class qubes --class gnu-linux --class gnu --class os --class xen \$menuentry_id_option 'xen-gnulinux-simple-/dev/mapper/qubes_dom0-root' {
insmod part_gpt
insmod ext2
search --no-floppy --fs-uuid --set=root $BOOT_UUID
echo 'Loading Xen ...'
if [ "\$grub_platform" = "pc" -o "\$grub_platform" = "" ]; then
xen_rm_opts=
else
xen_rm_opts="no-real-mode edd=off"
fi
insmod multiboot2
multiboot2 /$XEN_PATH placeholder console=none dom0_mem=min:1024M $DOM0_MAX_RAM ucode=scan smt=off gnttab_max_frames=2048 gnttab_max_maptrack_frames=4096 \${xen_rm_opts}
echo 'Loading Linux $LATEST_KERNEL ...'
module2 /$LATEST_KERNEL placeholder root=/dev/mapper/qubes_dom0-root ro rd.luks.uuid=$LUKS_UUID rd.lvm.lv=qubes_dom0/root rd.lvm.lv=qubes_dom0/swap plymouth.ignore-serial-consoles rhgb rootovl quiet kernel.sysrq=0 kernel.perf_event_paranoid=3 kernel.kptr_restrict=2 kernel.panic=5 fs.protected_regular=2 fs.protected_fifos=2 kernel.printk=3333 kernel.kexec_load_disabled=1 kernel.io_uring_disabled=2 lockdown=confidentiality module.sig_enforce=1 usbcore.authorized_default=0
echo 'Loading initial ramdisk ...'
insmod multiboot2
module2 --nounzip $LATEST_INITRAMFS
}
menuentry 'Qubes Zram-Live Mode (latest kernel)' --class qubes --class gnu-linux --class gnu --class os --class xen \$menuentry_id_option 'xen-gnulinux-simple-/dev/mapper/qubes_dom0-root' {
insmod part_gpt
insmod ext2
search --no-floppy --fs-uuid --set=root $BOOT_UUID
echo 'Loading Xen ...'
if [ "\$grub_platform" = "pc" -o "\$grub_platform" = "" ]; then
xen_rm_opts=
else
xen_rm_opts="no-real-mode edd=off"
fi
insmod multiboot2
multiboot2 /$XEN_PATH placeholder console=none dom0_mem=min:1024M $DOM0_MAX_RAM ucode=scan smt=off gnttab_max_frames=2048 gnttab_max_maptrack_frames=4096 \${xen_rm_opts}
echo 'Loading Linux $LATEST_KERNEL ...'
module2 /$LATEST_KERNEL placeholder root=/dev/mapper/qubes_dom0-root ro rd.luks.uuid=$LUKS_UUID rd.lvm.lv=qubes_dom0/root rd.lvm.lv=qubes_dom0/swap plymouth.ignore-serial-consoles rhgb rootzram quiet kernel.sysrq=0 kernel.perf_event_paranoid=3 kernel.kptr_restrict=2 kernel.panic=5 fs.protected_regular=2 fs.protected_fifos=2 kernel.printk=3333 kernel.kexec_load_disabled=1 kernel.io_uring_disabled=2 lockdown=confidentiality module.sig_enforce=1 usbcore.authorized_default=0
echo 'Loading initial ramdisk ...'
insmod multiboot2
module2 --nounzip $LATEST_INITRAMFS
}
EOF
chmod 755 /etc/grub.d/40_custom
# Update GRUB
grub2-mkconfig -o /boot/grub2/grub.cfg
echo "âś… ALL STEPS COMPLETED SUCCESSFULLY!"
If you want to add your custom kernel parameters for live modes, do it in sudo nano /etc/grub.d/40_custom and then update GRUB sudo grub2-mkconfig -o /boot/grub2/grub.cfg
![]()
Next Step:
Clone or create dangerous qubes (dvm‑template, appVMs) into a pool in dom0 for run this VMs in Live-Modes
Since only dom0 runs in live mode, you have to start the VMs from the pool in dom0.
In the Qube Manager click clone qube, then in Advanced select a pool in dom0: varlibqubes (or create your new pool in dom0).
Or create a new appVM, and select varlibqubes pool in the advanced settings.
Don’t select vm‑pool!
Use no more than two appVMs / DVMs if you only have 16 GB of memory.
Don’t add templates to varlibqubes - templates don’t retain any session data in an appVM (that’s the point of Qubes’ isolation). So, in live mode it’s sufficient that only the private and volatile storages are active. You can inspect all template metadata yourself and verify that none of them contain artifacts from sessions in appVM.
If you have a lot of RAM and want to run many appVMs in live mode, then resize dom0 storage (for example 30G) using these commands:
sudo lvresize --size 30G /dev/mapper/qubes_dom0-root
sudo resize2fs /dev/mapper/qubes_dom0-root
Done!
Restart Qubes OS and Test Qubes live modes ![]()
Now if dom0 updates kernels or you add more ram to device, just run script again and live modules and live grub config update kernel and ram-settings.
Zram-Live Mode takes longer to start (about 30-40 seconds more). Don’t worry about the errors when starting Zram-Live Mode – those are just informative errors indicating that a swap couldn’t be created.
Remember that the data will be erased only after a reboot of Qubes OS. All the memory will be wiped after Qubes is shutdown (like Tails).
You can update templates in live modes, but update dom0 in persistent mode!
If you create a qube in the vm‑pool while in live mode, this qube won’t be saved in the Qube Manager after a reboot.
You can make backups in live mode (I’ve done it many times using a Overlay-Live Mode).
![]()
You can add a “Generic Monitor” widget to the XFCE panel and configure it to run command findmnt -n -o SOURCE /. This widget will display which mode you’re currently in:
You can also use this terminal theme so can see which mode you’re currently in:
Click CTRL + H in thunar of dom0 and add this code into .bashrc instead of the default code:
# .bashrc
# Source global definitions
if [ -f /etc/bashrc ]; then
. /etc/bashrc
fi
# User specific environment
if ! [[ "$PATH" =~ "$HOME/.local/bin:$HOME/bin:" ]]
then
PATH="$HOME/.local/bin:$HOME/bin:$PATH"
fi
export PATH
###########################
export VIRTUAL_ENV_DISABLE_PROMPT=true
__qubes_update_prompt_data() {
local RETVAL=$?
__qubes_venv=''
[[ -n "$VIRTUAL_ENV" ]] && __qubes_venv=$(basename "$VIRTUAL_ENV")
__qubes_git=''
__qubes_git_color=$(tput setaf 10) # clean
local git_branch=$(git --no-optional-locks rev-parse --abbrev-ref HEAD 2> /dev/null)
if [[ -n "$git_branch" ]]; then
local git_status=$(git --no-optional-locks status --porcelain 2> /dev/null | tail -n 1)
[[ -n "$git_status" ]] && __qubes_git_color=$(tput setaf 11) # dirty
__qubes_git="‹${git_branch}›"
fi
__qubes_prompt_symbol_color=$(tput sgr0)
[[ "$RETVAL" -ne 0 ]] && __qubes_prompt_symbol_color=$(tput setaf 1)
return $RETVAL # to preserve retcode
}
if [[ -n "$git_branch" ]]; then
PROMPT_COMMAND="$PROMPT_COMMAND; __qubes_update_prompt_data"
else
PROMPT_COMMAND="__qubes_update_prompt_data"
fi
PS1=''
PS1+='\[$(tput setaf 7)\]$(echo -ne $__qubes_venv)\[$(tput sgr0)\]'
PS1+='\[$(tput setaf 14)\]\u'
PS1+='\[$(tput setaf 15)\] đź‘‘ '
PS1+='\[$(tput setaf 9)\]\h'
PS1+=" $(findmnt -n -o SOURCE /)"
PS1+='\[$(tput setaf 15)\]:'
PS1+='\[$(tput setaf 7)\]\w '
PS1+='\[$(echo -ne $__qubes_git_color)\]$(echo -ne $__qubes_git)\[$(tput sgr0)\] '
PS1+='\[$(tput setaf 8)\]\[$([[ -n "$QUBES_THEME_SHOW_TIME" ]] && echo -n "[\t]")\]\[$(tput sgr0)\]'
PS1+='\[$(tput sgr0)\]\n'
PS1+='\[$(echo -ne $__qubes_prompt_symbol_color)\]\$\[$(tput sgr0)\] '
If you have limited memory, use this guide: Really disposable (RAM based) qubes
Also see these guide:
USB Kill Switch for Qubes OS





