Quick check on Borg Backup. PoC running a backup of my home server and restoring the config on a VM.

Preparing a disk#

I have a external SSD Drive that i will use for this. To prepare it, is important to have a GPT paritition, different file system can be used, but i decided to go with XFS.

$ sudo fdisk /dev/sdb

Command (m for help): g
Created a new GPT disklabel (GUID: C1D811F3-D79F-46FF-87C5-38C1A8F04CE6).

Command (m for help): n
Partition number (1-128, default 1):
First sector (2048-937703054, default 2048):
Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-937703054, default 937701375):

Created a new partition 1 of type 'Linux filesystem' and of size 447.1 GiB.

Command (m for help): p
Disk /dev/sdb: 447.13 GiB, 480103981056 bytes, 937703088 sectors
Disk model: 2115
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: C1D811F3-D79F-46FF-87C5-38C1A8F04CE6

Device     Start       End   Sectors   Size Type
/dev/sdb1   2048 937701375 937699328 447.1G Linux filesystem

Command (m for help): w
The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.

Then format the disk and mount

$ sudo mkfs.xfs -f /dev/sdb1
$ sudo mount /dev/sdb1 /mnt

First backup with Borg#

First time using Borg

# install borg (fedora)
$ sudo dnf install borgbackup

# create your backup directory
$ sudo mkdir /mnt/borg-behelit-backup

# INIT BORG, this has to be done only once, and repokey will save
# the KEY inside the repository
$ sudo borg init --encryption=repokey /mnt/borg-behelit-backup

# start your backup of / and exclude direcotories you dont want to 
# backup with --exclude, --stats and --list will allow to watch the 
# process in real time
sudo borg create --stats --list --compression zstd,3 \
/mnt/borg-behelit-backup::full-$(date +%Y-%m-%d) / \
--exclude /proc \
--exclude /sys \
--exclude /dev \
--exclude /run \
--exclude /tmp \
--exclude /mnt \
--exclude /media \
--exclude /var/tmp \
--exclude /var/cache \
--exclude /var/log/journal

At the end you should see something like this:

------------------------------------------------------------------------------
Repository: /mnt/borg-behelit-backup
Archive name: full-2026-02-24
Archive fingerprint: 015d400d61917f623689a6caa7a2443d3f255477c0357b98b33ae40f2
Time (start): Tue, 2026-02-24 21:42:33
Time (end):   Tue, 2026-02-24 21:53:52
Duration: 11 minutes 18.53 seconds
Number of files: 255704
Utilization of max. archive size: 0%
------------------------------------------------------------------------------
                       Original size      Compressed size    Deduplicated size
This archive:               17.26 GB             12.10 GB             11.25 GB
All archives:               17.26 GB             12.10 GB             11.26 GB

                       Unique chunks         Total chunks
Chunk index:                  195793               244276
------------------------------------------------------------------------------

POC: Running a VM and restore my fresh backup#

Last time i run a VM was with Virtualbox. At work i have use proxmox but that seems like a bit too much. Will try qemu-kvm. Installation required for fedora:

$ sudo dnf5 install qemu-kvm libvirt virt-install
$ sudo systemctl enable --now libvirtd

Add user to relevant groups

$ sudo usermod -aG libvirt $(whoami)
$ sudo usermod -aG qemu $(whoami)

Enable virsh network

## This will allow to access network and run VMs as user
echo 'uri_default = "qemu:///system"' >> ~/.config/libvirt/libvirt.conf
virsh net-define /usr/share/libvirt/networks/default.xml
virsh net-autostart default
virsh net-start default
# Check that default network is running/active and it will autostart
virsh net-list --all
 Name      State    Autostart   Persistent
--------------------------------------------
 default   active   yes         yes

Start a fedora server installation

virt-install \                                
  --name fedora-berserk \                                                                                            
  --ram 4096 \
  --vcpus 2 \
  --disk path=/var/lib/libvirt/images/fedora-berserk.qcow2,size=20,format=qcow2 \
  --os-variant fedora-rawhide \
  --network network=default \
  --graphics none \
  --console pty,target_type=serial \
  --location 'https://download.fedoraproject.org/pub/fedora/linux/releases/43/Server/x86_64/os/' \
  --extra-args 'console=ttyS0,115200n8'

Once VM boots, it’s recommended to fix the IP address. Check your MAC address and fix it using dhcp. I did use the standard subnet that allows my VM to get internet access, required for later.

virsh net-edit default
## Add between <dhcp> tags </dhcp> a line like this:
<host mac='52:54:00:XX:XX:XX' name='VM-NAME' ip='192.168.122.2'/>
## Save file
virsh net-destroy
virsh net-start default
virsh shutdown VM-NAME
sudo systemctl restart libvirtd
virsh start VM-NAME
ping 192.168.122.2

## When ping reply you can ssh to your VM :)

Borg backup restore#

As my backup is on a external drive, i will pass the drive to the VM.

lsusb

## Identify your drive, in my case is as follow
Bus 001 Device 007: ID 174c:1153 ASMedia Technology Inc. ASM1153 SATA 3Gb/s bridge
## Note vendor ID 174c and Product ID 1153
  1. Create a XML file, usb.xml (remember to use your own IDs)
<hostdev mode='subsystem' type='usb' managed='yes'>
  <source>
    <vendor id='0x174c'/>
    <product id='0x1153'/>
  </source>
</hostdev>
  1. Attach the drive to the VM
virsh attach-device VM-NAME usb.xml --live
## If device was attached you should get
Device attached successfully

#### NOTE: To detach the device do:
virsh detach-device VM-NAME usb.xml --live
  1. Mount the drive in the VM

The device in my VM is show as /dev/sda, do lsblk to confirm.

lsblk 
NAME            MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
sda               8:0    0 447.1G  0 disk 
└─sda1            8:1    0 447.1G  0 part 
zram0           251:0    0   3.8G  0 disk [SWAP]
vda             253:0    0    20G  0 disk 
├─vda1          253:1    0     1M  0 part 
├─vda2          253:2    0     2G  0 part /boot
└─vda3          253:3    0    18G  0 part 
  └─fedora-root 252:0    0    15G  0 lvm  /

## Create a mount point
sudo mkdir -p /mnt/usb-backup
sudo mount /dev/sda1 /mnt/usb-backup
  1. The real deal!

VM can access the backup directly, first some backup integrity checks.

4.1 List archives

## It will ask for your passphrase
sudo borg list /mnt/usb-backup/borg-behelit-backup
## My output was
full-2026-02-24  Tue, 2026-02-24 21:42:33 [015d400d61917f623689a6caa7a2443d3f25547..


## Now verify (check) the repository.. it will take some time
sudo borg check --progress /mnt/usb-backup/borg-behelit-backup


## Extract a sample folder /etc
mkdir ~/recovery_backup
cd ~/recovery_backup
sudo borg extract --list /mnt/usb-backup/borg-behelit-backup::full-2026-02-24 etc

## You should have your backup /etc on the recovery_backup directory

4.2 Reinstall the packages

Aligned dnf repos

sudo dnf update
mkdir ~/old_repo
cd ~/old_repo
## extract /etc/ from backup
sudo borg extract /mnt/usb-backup/borg-behelit-backup::full-2026-02-24 etc
## copy the .repo which are not available in the fresh VM,
## in my case was docker-ce.repo
sudo cp -pr etc/yum.repos.d/docker-ce.repo /etc/yum.repos.d/
## Copy missing GPG Keys (if any, in my case there were none)
## /etc/pki/rpm-gpg/

## Refresh the cache
sudo dnf clean all
sudo dnf makecache

Installed all the packages to the new server. I made a list of installed packages and transfer to the new server:

## On the home server i did: 
rpm -qa > package.list

## And in the VM once i got the package.list, i did:

sudo dnf install --skip-unavailable $(cat package.list)

Pro TIP: Change the VM IP to the same IP as the original server, then is easier to test all the config that will be restored, shouldn’t need any adjusting as the IP will be the same. VM will be isolated as the VM GW is on another subnet.

After this point, all packages are the same on both servers. I did restore the config of some services like this:

cd ~/old_repo
sudo -s
## host file
cp -rp etc/hosts /etc/hosts
## certs
cp -rp etc/letsencrypt/* /etc/letsencrypt
## dhcpd
cp etc/dhcp/dhcpd.conf /etc/dhcp/dhcpd.conf
systemctl enable --now dhcpd
## postfix
cp -rp etc/postfix/* /etc/postfix/
## extract /var/spool/mail/
cd /
borg extract /mnt/usb-backup/borg-behelit-backup::full-2026-02-24 var/spool/mail
systemctl enable --now postfix
## dovecot
cd ~/old_repo
cp -rp etc/dovecot/* /etc/dovecot/
systemctl enable --now dovecot
## you get the point :D
## nginx
cp -rp etc/nginx/* /etc/nginx/
##
## extract /var/www from backup
cd /
borg extract /mnt/usb-backup/borg-behelit-backup::full-2026-02-24 var/www
## restore selinux
restorecon -Rv /var/www/html
systemctl enable --now nginx

## User migration
## I had to migrate 2 users only, just make sure of 
## having the same ID for the users in both systems
cd /
borg extract /mnt/usb-backup/borg-behelit-backup::full-2026-02-24 home/user1 /home/user2
## selinux
restorecon -Rv /home
##
## Quick email imap check can be done with mutt
mutt -f imap://user1@localhost
## imaps
mutt -f imaps://user1@localhost

Task done!

To be added: Docker migration

I have 2 dockers running but i couldn’t just restore them, working on it.