Ubuntu 24.04: Full Cold Backup of KVM/Libvirt with rsync

TL;DR — Power off all VMs → (optionally) stop or mask libvirt sockets so nothing auto-starts → rsync both /etc/libvirt and /var/lib/libvirt to /mnt/usb → (optional) generate checksums → (optional) do incrementals with --link-dest.
Scope: Backup-only. No restore steps.


What this backs up (and why)

This is a file-copy cold backup (no virsh dumpxml):

  • All libvirt config: /etc/libvirt/
    (domains in qemu/, networks, storage, secrets/, hooks/, nwfilter/, …)
  • All libvirt data: /var/lib/libvirt/
    • Disk images: /var/lib/libvirt/images/ (.qcow2, .img, .raw, ISO)
    • UEFI NVRAM: /var/lib/libvirt/qemu/nvram/ (critical for UEFI VMs)
    • Snapshots/boot: /var/lib/libvirt/qemu/snapshot/, /var/lib/libvirt/boot/
    • Runtime state: e.g., dnsmasq/ (harmless in a full copy)

If any VM disks live outside /var/lib/libvirt, add those paths to your backup.


Prerequisites

  • Ubuntu 24.04 with KVM/Libvirt
  • External drive mounted at /mnt/usb
    • Prefer ext4/xfs to preserve ACLs, xattrs, and sparse files (NTFS/FAT will mangle these).
  • Confirm mount:
mountpoint -q /mnt/usb && df -h /mnt/usb

  1. Power off all VMs (cold backup)

Graceful first; force off if needed.

# Graceful shutdown
for d in $(virsh list --state-running --name); do
  virsh shutdown "$d"
done

# Wait up to 60s
for i in {1..60}; do
  [ -z "$(virsh list --state-running --name)" ] && break
  sleep 1
done

# Force off if still running
for d in $(virsh list --state-running --name); do
  virsh destroy "$d"
done

# Sanity: no qemu processes (OK if empty)
pgrep -a qemu-system || true

  1. Prevent auto-start via socket activation

Ubuntu 24.04 uses systemd socket activation. Stopping services alone may not be enough. Stop sockets first; for a “no surprises” window, mask them temporarily.

# Stop sockets so libvirt can't respawn
sudo systemctl stop libvirtd.socket libvirtd-ro.socket libvirtd-admin.socket 2>/dev/null || true
sudo systemctl stop virtlogd.socket virtlogd-admin.socket virtlockd.socket virtlockd-admin.socket 2>/dev/null || true

# Stop services (ignore "not loaded")
sudo systemctl stop libvirtd virtlogd virtlockd 2>/dev/null || true

# (Stricter) temporarily mask sockets during backup
sudo systemctl mask libvirtd.socket libvirtd-ro.socket libvirtd-admin.socket
sudo systemctl mask virtlogd.socket virtlogd-admin.socket virtlockd.socket virtlockd-admin.socket

Quick checks (optional):

systemctl is-active libvirtd virtlogd virtlockd || true
systemctl is-active libvirtd.socket libvirtd-ro.socket libvirtd-admin.socket || true
systemctl is-active virtlogd.socket virtlogd-admin.socket virtlockd.socket virtlockd-admin.socket || true
ss -lntp | egrep 'libvirt|virt(log|lock|qemu)' || true

After your backup finishes, unmask + start to resume normal operation:

sudo systemctl unmask libvirtd.socket libvirtd-ro.socket libvirtd-admin.socket
sudo systemctl unmask virtlogd.socket virtlogd-admin.socket virtlockd.socket virtlockd-admin.socket
sudo systemctl start libvirtd

  1. Create a dated destination
DEST_ROOT="/mnt/usb"
DATE="$(date +%F_%H%M%S)"
DEST="$DEST_ROOT/kvm-full-backup-$DATE"

sudo mkdir -p "$DEST"/{etc-libvirt,var-libvirt}
sudo chmod 700 "$DEST"

  1. Full copy with rsync

Use -aHAX to preserve ownership/ACL/xattrs and --sparse to keep qcow2/raw “holes” compact.

# 1) All libvirt definitions & config
sudo rsync -aHAX --info=progress2 \
  /etc/libvirt/  "$DEST/etc-libvirt/"

# 2) All libvirt data (images, NVRAM, snapshots, dnsmasq, etc.)
sudo rsync -aHAX --sparse --info=progress2 \
  /var/lib/libvirt/  "$DEST/var-libvirt/"

sync
echo "✅ Full backup complete: $DEST"

  1. Generate checksums for images

Helpful for integrity verification later.

sudo find "$DEST/var-libvirt/images" -type f \
  \( -name '*.qcow2' -o -name '*.img' -o -name '*.raw' -o -name '*.iso' \) -print0 \
  | sudo xargs -0 -r sha256sum | sudo tee "$DEST/IMAGES_SHA256SUMS.txt" >/dev/null

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top