Expanding encrypted Btrfs storage by adding an extra partition

What am I talking about?

I’m assuming the following:

  • You have a Btrfs partition that you store data on. (I don’t care what data. Don’t tell me.)
  • You use LUKS to encrypt it.
  • You’re running out of space.
  • You have a way of obtaining additional space on your disk but you can’t just simply expand the existing partition.

If your situation differs, the rest of this post may be much less helpful.

Disclaimer

You’re following this guide at your own risk and I take no responsibility for any data loss, physical damage, or Portal Gun malfunctions. It could be risky.

Do have a backup of your data handy in case things go awry (a Btrfs snapshot does not count). Do have a bootable USB drive ready in case something goes really wrong.

Okay, here we go…

Make more space

I leave this part as an exercise for the reader, since circumstances may vary.

In my specific scenario, I had a dual-boot set-up with Windows and Ubuntu and I started running out of space for the latter1. I couldn’t extend the existing Btrfs partition any more—the only way to obtain additional storage space was to shrink the Windows partition.

And so I did.

I ended up shrinking it by about 200 GB and created a new partition in the newly emptied space, leaving me with:

% sudo parted /dev/nvme0n1 print
Number  Start   End     Size    File system  Name                          Flags
 ...
 3      290MB   274GB   273GB   ntfs         Basic data partition          msftdata
 6      274GB   483GB   210GB                Linux data (second device)
 5      483GB   1023GB  540GB                Linux data
 ...

Here 5 (or /dev/nvme0n1p5) is my main Linux data partition, and 6 (or /dev/nvme0n1p6) is the freshly added one.

Encrypt the new partition

It’s now time to encrypt the new partition. Using the same passphrase and keyfile that were used to encrypt the existing partition is likely to yield best results. This way, it should be enough to type the passphrase once at boot time to decrypt both.

Hence, we start with:

% sudo cryptsetup luksFormat /dev/nvme0n1p6

Which—if you do use a keyfile—should be followed by:

% sudo cryptsetup luksAddKey /dev/nvme0n1p6 /etc/luks/your.keyfile

where /etc/luks/your.keyfile is the path to your existing keyfile.

Update crypttab

We now need to update /etc/crypttab to ensure that the new partition is correctly decrypted during the boot sequence.

We’ll need the UUID of the new partition, and we can get it with blkid2:

% blkid /dev/nvme0n1p6 
/dev/nvme0n1p6: UUID="66cf1bcd-f756-4aa2-917f-1f25fa313731" TYPE="crypto_LUKS" PARTLABEL="Linux data (second device)" PARTUUID="5cc178a6-1049-4304-9473-61d3dd871e93"

Then we add a new line to /etc/crypttab (I’m following the convention to append _crypt to the device name, but use what makes sense for you), leaving us with something like this:

nvme0n1p5_crypt UUID=5efbd82c-29c3-443a-b028-ff21d91f78d0 /etc/luks/your.keyfile luks
nvme0n1p6_crypt UUID=66cf1bcd-f756-4aa2-917f-1f25fa313731 /etc/luks/your.keyfile luks

And now it’s a good time to test this configuration before proceeding further—you don’t want to discover that your configuration is incorrect during boot. Run:

% sudo cryptdisks_start nvme0n1p6_crypt
 * Starting crypto disk...
 * nvme0n1p6_crypt (starting)...
 * nvme0n1p6_crypt (started)...                    [ OK ]

This should decrypt /dev/nvme0n1p6t and create a new device at /dev/mapper/nvme0n1p6_crypt.

If you got an error instead, make sure that you used the correct UUID2.

If you did not get any errors, you can proceed to update initramfs. This step is crucial if you want to avoid surprises at boot time (such as Btrfs reporting a missing device):

% sudo update-initramfs -u
update-initramfs: Generating /boot/initrd.img-5.19.0-1012-lowlatency

Add the new Btrfs device

This is where we leverage one of the powers of Btrfs. Namely the one to create a single filesystem spanning multiple devices. (Note: we’re not setting up RAID here but that is an option as well.)

In the following scenario I’m assuming that /dev/mapper/nvme0n1p5_crypt is mounted on /. If your existing partition is mounted elsewhere, use that path instead. This is easy enough to check with something along the lines of:

% mount|grep nvme0n1p5_crypt
/dev/mapper/nvme0n1p5_crypt on / type btrfs (rw,relatime,ssd,space_cache,subvolid=256,subvol=/@)
...

We can add the second partition now3:

% sudo btrfs device add /dev/mapper/nvme0n1p6_crypt /

And let’s verify that it worked. You should be able to see something like this:

% sudo btrfs filesystem show
Label: none  uuid: f35c2a2e-db68-49a3-98a3-634eba37de92
	Total devices 2 FS bytes used 467.38GiB
	devid    1 size 502.62GiB used 502.62GiB path /dev/mapper/nvme0n1p5_crypt
	devid    2 size 195.30GiB used 0.00B path /dev/mapper/nvme0n1p6_crypt

And your total available space should increase:

% df -h
Filesystem                   Size  Used Avail Use% Mounted on
...
/dev/mapper/nvme0n1p5_crypt  698G  468G  229G  68% /
...
/dev/mapper/nvme0n1p5_crypt  698G  468G  229G  68% /swap
/dev/mapper/nvme0n1p5_crypt  698G  468G  229G  68% /home
...

And you’re (hopefully) done.

Reboot

Now for the big test: reboot your system. Once you reboot, sudo btrfs filesystem show should display the same info.

If something goes wrong, you’re likely to end up in an (initramfs) prompt where you can manually decrypt the new partition using the commands above, continue the booting process, and then proceed to fix your configuration.


  1. I stupidly thought that 1 TB was a lot of space, so splitting it in half between Windows and Ubuntu would be completely reasonable in case I ever needed Windows to run specific VSTs or play video games. ↩︎

  2. If you’re getting an outdated value (e.g. if you don’t see TYPE="crypto_LUKS" or the partition can’t be found in the following steps), try adding -p to blkid to bypass the cache, or try lsblk -o name,uuid↩︎ ↩︎

  3. Note that it has to be decrypted and the /dev/mapper/nvme0n1p6_crypt device must exist. You can achieve that with either cryptdisks_start nvme0n1p6_crypt in one of the previous steps or cryptsetup open /dev/nvme0n1p6 nvme0n1p6_crypt↩︎