linux-backup-system › Bootable Borg restore

How to restore a Borg backup to a new disk and make it boot

You have a Borg archive of your Linux system and a new, blank disk. borg extract will put every file back — and the machine will still not boot, or will boot by unlocking the old disk. This page explains what a file-level archive cannot carry onto a new disk, the manual steps that make it boot, and why you should test that before the day you need it.

Why a file-level archive is not a bootable restore

The archive holds files. Booting a disk depends on things that are tied to that disk — its ids, images built for it, the firmware's own memory — and every one of them is wrong, or missing, after a restore onto new partitions:

WhatWhy it breaks on a new disk
/etc/fstabNames the old filesystems by UUID=, PARTUUID= or LABEL=. New partitions have new ids.
/etc/crypttab, crypttab.initramfsName the old LUKS containers. mkinitcpio's sd-encrypt bakes crypttab.initramfs into the initramfs; left stale, the restored disk asks for the old container at every boot.
Kernel command linesroot=, resume=, rd.luks.uuid=, rd.luks.name=, cryptdevice= live in BLS and systemd-boot entries, /etc/kernel/cmdline and cmdline.d, GRUB_CMDLINE_LINUX and its drop-ins, extlinux.conf, syslinux.cfg, a Pi's cmdline.txt, refind_linux.conf, limine.conf. A stale rd.luks.uuid= stops the boot in the initramfs with every file present.
InitramfsBuilt for the old disk. It has to be regenerated after crypttab and the command lines are fixed.
Unified kernel images (UKIs)The kernel, initramfs and command line in one file. The embedded command line names the old disk, so the image has to be rebuilt — and kernel-install takes the command line from /etc/kernel/cmdline, so without one it can embed the rescue system's own.
BootloaderFirmware boot entries (NVRAM) are in no backup. A GRUB core image built on the source machine carries an early config naming that machine's disk (cryptomount -u, search --fs-uuid). Legacy BIOS GRUB lives in raw sectors no file-level backup holds.
Secure Boot signaturesEverything regenerated above is unsigned. A machine enrolled with its own keys refuses it until it is re-signed.
SELinux labelsA rescue system without SELinux may not write labels back, and a copy made without extended attributes has none. An enforcing system with unlabeled files refuses logins.
SwapfileAn active swapfile is in no file-level backup, and its resume_offset= changes when it is re-created.
Partition typesThe firmware boots only a partition typed EFI System; systemd-boot reads entries only from the ESP or a partition typed XBOOTLDR. Wrong types restore every file and still do not boot.

The quiet failure: if you restore from an installed system with the old disk still connected, the old ids still exist. A check that only asks “does this id exist?” passes, and the restored disk boots by unlocking — and running from — the old disk. The right check is that every boot-chain reference resolves to the new disk.

The manual outline

These are the steps in order, with the commands that perform them. Device names (/dev/sdX), the archive name and the repository path are yours to fill in.

1. Boot something that is not the target

Run the restore from a live USB, or from an installed system with the new disk attached as a second disk. Never restore over the root filesystem you are running from. From an installed system, also keep its firmware boot order untouched: install loaders with bootctl --no-variables install or grub-install --no-nvram --removable, write no efibootmgr entries, and pick the restored disk from the firmware boot menu.

2. Partition, encrypt and format the new disk like the old one

Recreate the layout the backed-up fstab describes: the ESP, a separate /boot if you had one, root, swap, /home, the LUKS containers and LVM volumes, the btrfs subvolumes. Then set the partition types the firmware and systemd-boot look for:

sgdisk -t N:ef00 /dev/sdX   # the ESP: EFI System
sgdisk -t N:ea00 /dev/sdX   # a vfat /boot beside it: XBOOTLDR

If GRUB opens an encrypted /boot, give that container a KDF your GRUB can open: argon2id needs GRUB 2.14 or later (2.12 has no argon2); pbkdf2 needs GRUB 2.02 or later for LUKS1 and 2.06 or later for LUKS2.

3. Mount the target tree the way fstab mounted it

cryptsetup open /dev/sdX3 <crypt_name>              # each LUKS container
vgchange -ay                                         # if root is on LVM
mount -o subvol=root /dev/mapper/<crypt_name> /mnt/target   # btrfs; ext4/LVM: mount /dev/mapper/<vg>-root /mnt/target
mkdir -p /mnt/target/home
mount -o subvol=home /dev/mapper/<crypt_name> /mnt/target/home
mkdir -p /mnt/target/boot
mount /dev/sdX2 /mnt/target/boot
mkdir -p /mnt/target/boot/efi
mount /dev/sdX1 /mnt/target/boot/efi                 # or /mnt/target/efi, where the source had it

4. Extract the archive into it

borg extract always writes into the current directory. Preview first; --dry-run does every step except writing:

cd /mnt/target
borg list /mnt/backup/borg-backup
borg extract --dry-run --list /mnt/backup/borg-backup::ARCHIVE
borg extract --list --numeric-ids /mnt/backup/borg-backup::ARCHIVE

--numeric-ids keeps owners as the numbers the archive recorded. Without it, owners are mapped by name through the rescue system's user database: restoring Fedora from a Manjaro stick remapped every system account the two distros number differently. An exit code of 1 is a warning (for example, extended attributes the target filesystem cannot take) — read the list, do not stop there.

5. Rewrite every id to the new disk

Read the new ids with lsblk -f (or blkid). Then, under /mnt/target, rewrite fstab, crypttab, crypttab.initramfs and every command-line carrier from the table above — field by field, in the form each entry already uses (UUID=, PARTUUID=, LABEL=), leaving device-mapper names alone. Include any full GRUB configuration a generator will not rewrite, and the small ESP grub.cfg that finds /boot by search --fs-uuid.

6. Chroot in and rebuild the boot chain

mount --bind /dev /mnt/target/dev
mount --bind /dev/pts /mnt/target/dev/pts
mount -t proc proc /mnt/target/proc
mount -t sysfs sys /mnt/target/sys
mount --bind /run /mnt/target/run
chroot /mnt/target /bin/bash

On a UEFI system, also bind /sys/firmware/efi/efivars if the bootloader install should write a firmware entry. Then rebuild what the restored system actually uses:

# initramfs — one of:
dracut --regenerate-all --force           # Fedora family
update-initramfs -c -k <kernel-version>    # Debian family, per installed kernel
mkinitcpio -P                             # Arch family

# GRUB (EFI): reinstall, then regenerate its config
grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=<id> --recheck
grub-mkconfig -o /boot/grub/grub.cfg       # Fedora: grub2-mkconfig -o /boot/grub2/grub.cfg

# systemd-boot
bootctl install

# a UKI or Type #1 entry through kernel-install (after /etc/kernel/cmdline names the new disk)
kernel-install add <kernel-version> /lib/modules/<kernel-version>/vmlinuz

# Secure Boot with your own keys (sbctl)
sbctl sign-all
sbctl verify

Three exceptions catch people. On Fedora with shim on the ESP, keep the signed GRUB image and do not run grub2-install — it would replace it with an unsigned one. Where UKIs come from mkinitcpio presets, rebuild them with mkinitcpio -P, not kernel-install, which would add a second set of entries. And bootctl install writes a fresh, unsigned systemd-boot that sbctl sign-all does not know about; under your own Secure Boot keys, sign that file with sbctl sign -s before rebooting. (On aarch64, GRUB's target is arm64-efi.)

7. Swapfile and SELinux

Re-create any swapfile fstab names, and if you hibernate into it, point resume_offset= at its new blocks (on btrfs, btrfs inspect-internal map-swapfile -r prints it). On an SELinux system, still inside the chroot:

touch /.autorelabel

The first boot relabels every file and reboots once.

8. Check before you reboot

Search the restored /etc, /boot and ESP for every old id you replaced. A reference that names a missing id will stop the boot; one that names an id on another connected disk will boot, unlock or mount that disk. Neither should be left.

Test the restore before you need it

A backup you have never restored is a hope, not a backup. The only proof that the steps above work for your layout is to do them: restore onto a spare drive, boot it from the firmware menu, log in, check the network, compare the restored files against the archive, and confirm your own disks were not written. Then do it again after any change to the boot layout.

The test is what finds the faults no check does. On real machines, test restores have found:

No spare drive? A disk image on the backup drive, booted in a VM, proves the restore — but not that your machine's firmware will boot it. Count it as that and no more.

Automated: linux-backup-system runs this whole outline as one restore, from a live USB or an installed system: it checks the new disk's partition types, extracts with --numeric-ids, rewrites fstab, crypttab and every command-line carrier to the new disk's ids, re-creates swapfiles and creates /.autorelabel, then chroots in and rebuilds the initramfs or UKI, GRUB or systemd-boot — detected, not configured — and re-signs with sbctl where it finds its keys. It refuses to call the restore complete while anything resolves to a disk other than the target. A daily check asserts from the backup drive that a restore would boot, and a restore test bed turns the test above into a verdict: PASS for a drive the machine booted, PASS-VM for a disk image booted only in a VM. Every script takes --dry-run.

What is verified, and what is not

A ✅ below is a real backup, a passing verify, and a restore onto a blank disk that booted, logged in and came up on the network, byte-compared against the archive. The tables rest on 10 verified systems, plus an eleventh — an M2 Max running Fedora Asahi Remix — restored onto a disk image and booted in a VM (PASS-VM). A PASS-VM is evidence for the restore, never for the machine's firmware booting it, and it never turns a row ✅ on its own.

✅ verified⚠️ under test❌ wanted
DistrosFedora; Fedora Asahi Remix (aarch64, over a fresh Asahi install, never bare metal); Debian / Ubuntu / Linux Mint; Arch / Manjaro / EndeavourOS—openSUSE; Slackware, Gentoo, Alpine, Void, NixOS, Solus
Root filesystemsbtrfs (subvolumes, swapfile), ext4, root on LUKS2 (sd-encrypt), LVM-on-LUKS—xfs, f2fs
Boot layoutsGRUB (EFI); systemd-boot (Type #1 and UKI); UKIs from dracut, kernel-install and mkinitcpio; Secure Boot with sbctl keys; encrypted argon2id /boot; plain /boot; ESP at /efi + XBOOTLDR; SELinux relabel (enforcing and permissive)Secure Boot through shim; restore from a live USBGRUB legacy BIOS; encrypted pbkdf2 /boot; Raspberry Pi firmware boot; Limine; rEFInd

Not handled by the restore yet: image-based distros (ostree, transactional), NixOS, ZFS and bcachefs roots, and mdadm RAID.

Testing only unless your setup is all green. Unless your distro, root filesystem and boot layout are all ✅ in the Bare-metal restore column of the status table, use this for testing, not production.

Frequently asked questions

Can I restore a Borg backup onto a smaller or larger disk?

Yes, as long as what the archive holds fits. A file-level restore does not care how big the old partitions were: you create the new ones, and the ids in fstab, crypttab and the command lines are rewritten to match them either way.

Why does my restored system stop in the initramfs?

A common cause is a stale id. A kernel command line with the old rd.luks.uuid=, root= or cryptdevice=, or an initramfs that was built before crypttab was fixed, asks for a container or filesystem that is not on the new disk, so the boot stops in the initramfs with every file present. Fix every carrier, then regenerate the initramfs.

Do I have to re-sign anything for Secure Boot after a restore?

With your own keys, yes: every loader, initramfs and UKI the rebuild regenerates is unsigned, and the firmware refuses them until they are signed again, for example with sbctl sign-all and a check with sbctl verify. With a distro's shim, keep the distro's signed loaders instead of reinstalling them.

Why use --numeric-ids when extracting?

Without it, borg maps owners by name through the rescue system's own user database. Two distros number their system accounts differently, so restoring one distro from another's live USB can give every system file the wrong owner. --numeric-ids keeps the numbers the archive recorded.

Does the restored disk need a new firmware boot entry?

Firmware boot entries are in no backup. From a live USB the bootloader install can write one. From an installed system, write none, so the running machine's boot order is left alone: install the loader to the removable-media fallback path and pick the restored disk from the firmware boot menu.

Can I get back a few files without a full restore?

Yes. borg mount mounts a repository or archive as a FUSE filesystem you can browse and copy from, and borg extract can take paths inside the archive to extract only those, into the current directory.

Sources