My laptop had Ubuntu with full-disk encryption on the whole drive, and I needed Windows next to it. Most dual-boot guides assume a plain disk and stop working once encryption is involved. This is what worked for me, with Ubuntu installed first and Windows added second.
Every command block says where it runs. Some run on your installed Ubuntu, some on the live USB, and running one in the wrong place can break things. The outputs are from my machine, trimmed a little. Your sizes and IDs will be different.
Installed Ubuntu · Root shell
sudo rsync -aAXH --info=progress2 / /mnt/backup \
--exclude={"/dev/*","/proc/*","/sys/*","/run/*","/tmp/*","/mnt/*","/media/*","/lost+found"}
sudo cryptsetup luksHeaderBackup /dev/nvme0n1p3 \
--header-backup-file /mnt/backup/luks-header.imgoutput
6,318,442,496 41% 5.40MB/s 0:18:22 (xfr#412183, to-chk=18002/2600035)If rsync ends with exit code 23, some files didn’t copy. Scroll up and check which ones. On my machine they were all virtual files under /var/lib/lxcfs, which you don’t need, but don’t assume that until you’ve looked.
Why it takes two steps
An encrypted Ubuntu install is three layers inside each other. The disk partition holds a LUKS container (the encryption). Inside that is LVM, which splits the space into volumes. Inside LVM is the ext4 filesystem with your files.
You shrink from the inside out. First the filesystem and its volume, then the partition around them:
411 GiB · your files
partition still full size
411 GiB · your files
on disk → for Windows
Find your device names
On your installed system, check what your disk and partitions are called:
Installed Ubuntu · Terminal
lsblk -o NAME,SIZE,FSTYPE,MOUNTPOINToutput
NAME SIZE FSTYPE MOUNTPOINT
nvme0n1 476.9G
├─nvme0n1p1 512M vfat /boot/efi ← EFI
├─nvme0n1p2 1.4G ext4 /boot ← boot
└─nvme0n1p3 476G crypto_LUKS ← LUKS
└─nvme0n1p3_crypt 476G LVM2_member ← LVM
├─vgubuntu-root 475G ext4 / ← your files
└─vgubuntu-swap_1 976M swap [SWAP]NVMe disks are called nvme0n1, SATA disks sda. Every command below uses the names from my machine, so swap in yours. A wrong name means a wrong disk.
Mine are:
- LUKS partition:
nvme0n1p3 /boot:nvme0n1p2- EFI:
nvme0n1p1 - whole disk:
nvme0n1
Note the name of the unlocked device too (nvme0n1p3_crypt here). You’ll reuse it from the live USB so the paths match.
Shrink Ubuntu
Boot the Ubuntu live USB and pick Try Ubuntu. Everything in this section runs there, because you can’t shrink a filesystem while it’s mounted and in use.
Unlock the partition. It asks for your passphrase:
Live USB · Terminal
sudo cryptsetup luksOpen /dev/nvme0n1p3 nvme0n1p3_cryptIt prints nothing if it worked. A wrong passphrase gives No key available with this passphrase.
Check the LVM volumes:
Live USB · Terminal
sudo lvsoutput
LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert
root vgubuntu -wi-a----- <475.00g
swap_1 vgubuntu -wi-a----- 976.00mIf this shows nothing, run sudo vgchange -ay and try again.
Now shrink root by 64 GiB. The -r makes LVM resize the ext4 filesystem along with the volume. Without it you’d cut the volume under a filesystem that still thinks it’s full size, and you’d lose data.
64 GiB is just over Windows 11’s 64 GB minimum. Take more if you’ll install anything on Windows.
Live USB · Terminal
sudo lvreduce -r -L -64G /dev/vgubuntu/rootoutput
Found volume group "vgubuntu"
fsck from util-linux 2.37.2
/dev/mapper/vgubuntu-root: clean, 1.2M/30M files, 80G/124G blocks
resize2fs 1.46.5 (30-Dec-2021)
Resizing the filesystem on /dev/mapper/vgubuntu-root to 107515904 (4k) blocks.
The filesystem on /dev/mapper/vgubuntu-root is now 107515904 (4k) blocks long.
Size of logical volume vgubuntu/root changed from <475.00 GiB to <411.00 GiB.
Logical volume vgubuntu/root successfully resized.Then the partition, in GParted:
crypt-luks partition (/dev/nvme0n1p3).
2Click Resize/Move and enter your passphrase if it asks.
3Make it smaller by the 64 GiB you freed, and no more.
4Click Apply.
With the resize queued, it looks like this:
| Partition | File System | Size | Used |
|---|---|---|---|
| 🔒 /dev/nvme0n1p3 | crypt-luks | 411.97 GiB | ~80 GiB |
| unallocated | unallocated | 64.00 GiB | — |
If GParted won’t shrink it
Mine failed the first time with “cannot resize, later extents are allocated”. Something was still sitting at the end of the partition. Look at where each volume is:
Live USB · Terminal
sudo pvs --segments -o pv_name,lv_name,seg_start_pe,seg_size_peoutput
PV LV Start SSize
/dev/mapper/nvme0n1p3_crypt root 0 105215
/dev/mapper/nvme0n1p3_crypt 105215 16384
/dev/mapper/nvme0n1p3_crypt swap_1 121599 250root is at the front. The middle row has no LV name, so that’s the free space lvreduce made. Swap comes after it, at the very end, and that’s what blocks the shrink. GParted can only cut free space off the end.
The fix is to move swap into the gap right after root. Swap starts at extent 121599 and is 250 extents long, so it covers 121599-121848 (the end is start + size − 1). The gap starts at 105215, so the target is 105215-105464:
Live USB · Terminal
sudo pvmove --alloc anywhere \
/dev/mapper/nvme0n1p3_crypt:121599-121848 \
/dev/mapper/nvme0n1p3_crypt:105215-105464output
/dev/mapper/nvme0n1p3_crypt: Moved: 5.08%
/dev/mapper/nvme0n1p3_crypt: Moved: 41.27%
/dev/mapper/nvme0n1p3_crypt: Moved: 100.00%Use your own numbers from pvs --segments. Wait for 100.00%, then run pvs --segments again and check that the free row is now last. After that the GParted resize goes through.
Make the Windows USB
Rufus is the usual tool for this, but it only runs on Windows. On Linux most people use WoeUSB, and it has a catch.
Windows 11’s install.wim is bigger than 4 GB (7.06 GB in my ISO), and FAT32, the filesystem UEFI can boot from, can’t hold a file over 4 GB. So WoeUSB puts the files on an NTFS partition and adds a small bootloader, UEFI:NTFS, which loads an NTFS driver at boot. On some machines that driver fails to start:
UEFI:NTFS: Unable to start driver: [2] Invalid ParameterThat’s what happened on mine. You can get around it with FAT32 by splitting install.wim into smaller files, but I found Ventoy easier. You install it on the stick once, then just copy ISOs onto it:
Installed Ubuntu · Terminal
# erases the stick, check it's really /dev/sda with lsblk first
sudo sh Ventoy2Disk.sh -i /dev/sda
sudo mkdir -p /mnt/ventoy
sudo mount /dev/sda1 /mnt/ventoy
sudo cp ~/Downloads/Win11.iso /mnt/ventoy/
sync
sudo umount /mnt/ventoyoutput of the Ventoy install
Disk : /dev/sda
Model: SanDisk Cruzer Blade (scsi)
Size : 14 GB
Continue? (y/n) y
Double-check. Continue? (y/n) y
Create partitions on /dev/sda by parted in MBR style ...
Done
Install Ventoy to /dev/sda successfully finished.The cp shows no progress and it’s slow. An 8.5 GB ISO took about 25 minutes on my cheap stick. Wait for umount to finish before you pull it out.
Boot the stick in UEFI mode. If your boot menu lists it twice, pick the entry that starts with UEFI:. Booted the other way, Windows setup won’t install onto a GPT disk like this one.
Install Windows
To skip the Microsoft account, I pressed Shift+F10 on the network screen and ran:
Windows Setup · Shift+F10 prompt
OOBE\BYPASSNROSetup restarted with an I don’t have internet option, and Continue with limited setup gave me a local account. Microsoft has removed this from newer Windows 11 builds, so it may not work with your ISO.
Get back into Ubuntu
After the install, the laptop boots straight into Windows. Ubuntu is still there. Windows just put its own entry first in the UEFI boot order.
So try the simple fix first. Open your firmware’s boot menu (F12 on many laptops), pick ubuntu, and once you’re in, move it back to the top:
Installed Ubuntu · Terminal
sudo efibootmgr # lists entries and the current BootOrder
sudo efibootmgr -o 0001,0000 # your ubuntu entry's number first, then WindowsIf there’s no ubuntu entry at all, reinstall GRUB from the live USB. The live system can’t see your installed Ubuntu until you unlock and mount it, so do that first:
Live USB · Terminal
sudo cryptsetup luksOpen /dev/nvme0n1p3 nvme0n1p3_crypt
sudo mount /dev/mapper/vgubuntu-root /mnt
sudo mount /dev/nvme0n1p2 /mnt/boot
sudo mount /dev/nvme0n1p1 /mnt/boot/efi
findmnt -R /mntoutput
TARGET SOURCE FSTYPE OPTIONS
/mnt /dev/mapper/vgubuntu-root ext4 rw,relatime
└─/mnt/boot /dev/nvme0n1p2 ext4 rw,relatime
└─/mnt/boot/efi /dev/nvme0n1p1 vfat rw,relatimeThen switch into it and install GRUB to the EFI partition. GRUB’s own tools have to run from inside your installed system, which is why you need chroot and the extra mounts:
Live USB · Terminal
for d in dev proc sys run; do sudo mount --rbind /$d /mnt/$d; done
sudo chroot /mnt grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=ubuntu
sudo chroot /mnt update-gruboutput
Installing for x86_64-efi platform.
Installation finished. No error reported.Reboot, and GRUB is back.
Add Windows to the GRUB menu
GRUB only lists Ubuntu at first. It finds other systems with a tool called os-prober, and Ubuntu ships with that turned off. On your installed system:
Installed Ubuntu · Terminal
sudo nano /etc/default/grubFind this line near the bottom and remove the #:
in the editor
- #GRUB_DISABLE_OS_PROBER=false
+ GRUB_DISABLE_OS_PROBER=falseIf the line isn’t there, add it. Save with Ctrl+O, Enter, then Ctrl+X, and rebuild the menu:
Installed Ubuntu · Terminal
sudo update-gruboutput
Sourcing file `/etc/default/grub'
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-6.8.0-124-generic
Found initrd image: /boot/initrd.img-6.8.0-124-generic
Found Windows Boot Manager on /dev/nvme0n1p1@/EFI/Microsoft/Boot/bootmgfw.efi
doneYou want the Found Windows Boot Manager line. If it’s missing, check that the change saved and that the os-prober package is installed (sudo apt install os-prober).
Reboot, and the menu has both systems. If a Windows update ever puts its entry back on top, fix it the same way, with the firmware boot menu and efibootmgr.