Dual-booting Windows next to an encrypted Ubuntu

Shrinking a LUKS + LVM Ubuntu to dual-boot Windows — the exact steps, and the failures most guides skip.

← Writing

Most dual-boot guides assume a plain disk. They break on full-disk encryption. This is the version for an encrypted Ubuntu — LUKS with LVM inside — installed first, Windows added second.

Each command block is tagged with where it runs — run it anywhere else and you break things.

The output blocks are representative — real values from this machine, formatting trimmed for readability. Yours will differ in sizes and IDs.

Installed Ubuntu · Root shell

sudo rsync -aAXH --info=progress2 / /mnt/backup \
  --exclude={"/dev/*","/proc/*","/sys/*","/run/*","/tmp/*","/mnt/*","/media/*","/lost+found"}

output

  6,318,442,496  41%    5.40MB/s    0:18:22 (xfr#412183, to-chk=18002/2600035)

Exit code 23 (some files/attrs were not transferred) is usually just virtual files under /var/lib/lxcfs — harmless. Finish before booting the installation media.

The Layout

Encryption stacks three layers, each one nested inside the one before it:

Shrinking takes two separate steps, because the virtual layers and the physical partition can’t shrink together:

After lvreduce virtual layers shrunk
LUKS partition · 475 GiB · unchanged
LVM · vgubuntu · 411 GiB
ext4 · root
410 GiB · your files
64 GiB slack — freed inside,
partition still full size
After GParted partition shrunk to match
LUKS partition · 411 GiB
LVM · vgubuntu · 411 GiB
ext4 · root
410 GiB · your files
64 GiB unallocated
on disk → for Windows
1lvreduce shrinks the virtual layers. ext4 and LVM drop to 411 GiB. The LUKS partition can't shrink itself, so it stays 475 GiB — now with 64 GiB of empty slack inside (left panel). 2GParted shrinks the partition to match. It trims the LUKS partition down to 411 GiB, turning that internal slack into real unallocated space on the disk (right panel). !Both steps are required. Do only step 1 and the freed space is trapped inside the partition — the Windows installer sees nothing it can use.

Device Names

First, on your running system, learn your disk’s real names:

Installed Ubuntu · Terminal

lsblk -o NAME,SIZE,FSTYPE,MOUNTPOINT

output

NAME                    SIZE FSTYPE      MOUNTPOINT
nvme0n1               476.9G
├─nvme0n1p1             512M vfat        /boot/efi      ← EFI
├─nvme0n1p2             1.4G ext4        /boot          ← boot
└─nvme0n1p3             475G crypto_LUKS                ← LUKS
  └─nvme0n1p3_crypt     475G LVM2_member                ← LVM
    ├─vgubuntu-root     474G ext4        /              ← your files
    └─vgubuntu-swap_1   976M swap        [SWAP]

Device names differ by hardware: SATA disks are sda, NVMe disks are nvme0n1. Every command below uses the names from this example — substitute your own. Wrong name, wrong disk.

This example uses:

  • LUKS partition: nvme0n1p3
  • /boot: nvme0n1p2
  • EFI: nvme0n1p1
  • whole disk: nvme0n1

Shrink Ubuntu

Now boot the Linux installation media and pick Try Ubuntu. Everything in this section runs from the installation media, not your installed system — you can’t resize a filesystem that’s mounted and running.

Unlock the partition (prompts for passphrase):

Installation Media · Terminal

sudo cryptsetup luksOpen /dev/nvme0n1p3 cryptdisk

Returns silently on success; a wrong passphrase says No key available with this passphrase.

Check the LVM layout:

Installation Media · Terminal

sudo lvs

output

  LV     VG       Attr       LSize    Pool Origin Data%  Meta%  Move Log Cpy%Sync Convert
  root   vgubuntu -wi-ao---- <475.00g
  swap_1 vgubuntu -wi-ao----  976.00m

Shrink the volume. -r resizes the ext4 filesystem with the LVM volume — without it you corrupt the filesystem. 64 GB is the Windows 11 minimum:

Installation Media · Terminal

sudo lvreduce -r -L -64G /dev/vgubuntu/root

output

  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.

Now the partition — in GParted:

1Select the crypto-luks partition (/dev/nvme0n1p3). 2Click Resize/Move and enter your passphrase when asked. 3Drag the right edge inward by the 64 GB you freed. 4Click Apply.

The window looks like this with the resize staged:

/dev/nvme0n1 - GParted 476.94 GiB
GPartedEditView DevicePartitionHelp
NewDeleteResize/MoveCopyPasteApply /dev/nvme0n1 (476.94 GiB) ▾
/dev/nvme0n1p3crypt-luks · 410.97 GiB
unallocated64.00 GiB
PartitionFile SystemSizeUsed
🔒 /dev/nvme0n1p3 crypt-luks410.97 GiB~80 GiB
unallocated unallocated64.00 GiB
1 operation pending — Shrink /dev/nvme0n1p3 from 474.97 GiB to 410.97 GiB
The freed 64 GB becomes grey unallocated space at the end of the disk — where the Windows installer lands.

If the Partition Resize Fails

Error: “cannot resize, later extents are allocated.” Something sits at the end of the disk — usually swap. Find it:

Installation Media · Terminal

sudo pvs --segments -o pv_name,lv_name,seg_start_pe,seg_size_pe

output

  PV                          LV      Start  SSize
  /dev/mapper/nvme0n1p3_crypt root        0 105215
  /dev/mapper/nvme0n1p3_crypt             105215  16384
  /dev/mapper/nvme0n1p3_crypt swap_1 121599    250

Read the LV column. root fills the front. The middle row has no LV name — that’s the free space. Then swap_1 sits after it, at the very end. That trailing swap is what blocks the shrink: GParted can’t trim the partition past an allocated extent. Move swap out of the way so the free space ends up last.

The range to move is swap_1’s Start plus its SSize — here 121599 to 121599+250, so 121599-121848. Move it:

Installation Media · Terminal

sudo pvmove --alloc anywhere /dev/mapper/nvme0n1p3_crypt:121599-121848

output

  /dev/mapper/nvme0n1p3_crypt: Moved: 5.08%
  /dev/mapper/nvme0n1p3_crypt: Moved: 41.27%
  /dev/mapper/nvme0n1p3_crypt: Moved: 100.00%

Use your own range from the pvs --segments output. Wait for 100.00%. Swap now sits next to root, free space last. Resize in GParted again — it applies, and the unallocated space lands at the end of the disk.

Windows Media

Rufus, the usual answer, is Windows-only — no help when Linux is all you have. On Linux the common tool is WoeUSB. It works, but it has a trap.

Windows 11’s install.wim is larger than 4 GB (7.06 GB in this ISO), and FAT32 — the filesystem UEFI boots natively — caps any single file at 4 GB. So WoeUSB writes the files to an NTFS partition and adds a small bootloader, UEFI:NTFS, that loads an NTFS driver at boot so the firmware can read it. On some firmware that driver refuses to start:

UEFI:NTFS — Unable to start driver: [2] Invalid Parameter

The USB is fine. The firmware just can’t read NTFS to boot it. Reformatting to FAT32 doesn’t help — the 7 GB file won’t fit.

Use Ventoy instead — it boots ISOs without native NTFS, and you copy ISOs on instead of reflashing:

Installed Ubuntu · Terminal

# erases the stick
sudo sh Ventoy2Disk.sh -i /dev/sda

sudo mount /dev/sda1 /mnt/ventoy
sudo cp ~/Downloads/Win11.iso /mnt/ventoy/
sync

output 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 is slow — an 8.5 GB ISO to a cheap stick took ~25 minutes. Wait for the prompt, sync, then pull it.

In the boot menu the USB appears twice — plain and UEFI:. Pick UEFI:, or Windows installs in legacy mode and won’t dual-boot cleanly.

Install Windows

1Boot the Ventoy stick and pick the Windows ISO from its menu. 2Run setup, choose Custom install. 3Select the unallocated space you freed earlier — not an existing partition. 4Let it install and reboot into Windows.

Optional — skip the forced Microsoft account. At the network screen, press Shift+F10 and run:

Windows Setup · Shift+F10 prompt

OOBE\BYPASSNRO

It reboots to the network screen, now showing I don’t have internet. Pick that, then Continue with limited setup for a local account.

Restore GRUB

After install, the machine boots straight to Windows. Ubuntu isn’t gone — Windows overwrote the bootloader. Boot the Linux installation media again.

You’re now in the temporary installer system, which can’t see your installed Ubuntu — it’s encrypted and unmounted. grub-install has to read that system’s config and write into its EFI partition, so you expose it first: unlock the encryption, then mount root, /boot (the kernels), and the EFI partition where the bootloader is written.

Installation Media · Terminal

sudo cryptsetup luksOpen /dev/nvme0n1p3 cryptdisk  # 1. unlock encryption
sudo mount /dev/mapper/vgubuntu-root /mnt           # 2. mount root
sudo mount /dev/nvme0n1p2 /mnt/boot                 # 3. mount /boot (kernels)
sudo mount /dev/nvme0n1p1 /mnt/boot/efi            # 4. mount EFI (bootloader)

Confirm the mounts stuck:

Installation Media · Terminal

findmnt /mnt /mnt/boot /mnt/boot/efi

output

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,relatime

Reinstall GRUB to the whole disk — no partition number:

Installation Media · Terminal

sudo grub-install --root-directory=/mnt /dev/nvme0n1

output

Installing for x86_64-efi platform.
Installation finished. No error reported.

/dev/nvme0n1, not nvme0n1p1. Reboot. GRUB is back.

Windows in GRUB

GRUB lists only Ubuntu until you enable OS detection, which modern Ubuntu ships off. Back in your installed system:

Installed Ubuntu · Terminal

sudo nano /etc/default/grub

Find this line near the bottom — it ships commented out — and remove the leading #:

in the editor

- #GRUB_DISABLE_OS_PROBER=false
+ GRUB_DISABLE_OS_PROBER=false

If the line isn’t there at all, add it. Save with Ctrl+O, Enter, then Ctrl+X. Then:

Installed Ubuntu · Terminal

sudo update-grub

output

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
done

No Found Windows Boot Manager line means OS prober is still off. Reboot. Both systems in the menu.

Summary

  • Use your real device names — nvme0n1, not sda.
  • lvreduce -r resizes the filesystem with the volume. Don’t drop the -r.
  • You need bootable Windows media — Ventoy makes it from Linux.
  • Keep the installation media — you need it twice: shrink, then restore GRUB.
  • grub-install targets the disk, not a partition.
  • Back up before the shrink.