Automating Efficient VM Backups with Ansible, virtnbdbackup, and Borg
I always wanted a simple and flexible way to back up my KVM virtual machines.
In the past, I relied on custom scripts using rsync to copy disk images to a backup location. It worked – but with some annoying downsides: downtime during backup, large backup sizes, and manual effort.
At some point, I switched to Proxmox because of its sleek backup system – fast, reliable, and easy to manage. But things changed, and I went back to vanilla KVM setups.
Recently, I found a fascinating solution with virtnbdbackup that fits perfectly into my existing toolchain:
Ansible + Virtnbdbackup + Borgbackup.
Since I already use Ansible for automation, and I’m a big fan of Borg because of its deduplication and reliability, this setup felt like a natural upgrade. And the best part: no VM downtime during backup.
In this post, I’ll show you how I set it up, why it rocks, and how you can integrate it easily into your own infrastructure.
I will not cover here the installation of ansible and borgbackup, both have exellent dokumentation:
https://borgbackup.readthedocs.io/en/latest/quickstart.html
https://docs.ansible.com/ansible/latest/installation_guide/installation_distros.html
For ansible i have created a dedicated ansibe user with login permissions via ssh-key and is in group sudo.
Folder structure
.
├── group_vars
│ ├── all
│ │ └── ansible_user_vars.yaml
│ ├── borg
│ │ └── borg_passphrase.yaml
│ └── kvmHost
│ └── virtnbdbackup.yaml
├── host_vars
│ ├── dost.yaml
│ └── stor.yaml
├── production
├── staging
├── Makefile
├── playbooks
│ ├── kvmVM_backup.yaml
│ └── software_virtnbdbackup.yaml
└── roles
Create vault passphrase
openssl rand -base64 32 > ~/.vault_pass.txt
chmod 600 ~/.vault_pass.txt
Install sofware virtnbdbackup
Since virtnbdbackup only needs to be installed once on the KVM host, we separate the process
into two playbooks: one for installing the software, and a second for running the backups.
Create the file: group_vars/borg/virtnbdbackup.yaml
virtnbdbackup_repo: https://github.com/abbbi/virtnbdbackup.git
virtnbdbackup_dir: /opt/virtnbdbackup
Create ansible_user_vars
Create the file: group_vars/all/ansible_user_vars.yaml
ansible_user_name: ansible
ansible_user_shell: /bin/bash
ansible_user_groups: ['sudo']
ansible_user_ssh_key: "{{ lookup('file', lookup('env','HOME') + '/.ssh/id_ansible.pub') }}"
sudoers_file_path: "/etc/sudoers.d/{{ ansible_user_name }}"
ansible_user_password: "<password for user ansible>"
and encrytp that file
ansible-vault encrypt group_vars/all/ansible_user_vars.yaml
Create Borg Passwordfile
Create the file: group_vars/borg/borg_passphrase.yaml
borg_passphrase: <borg repo init passphrase>
and encrypt also
ansible-vault encrypt group_vars/borg/borg_passphrase.yaml
Create Inventory
Create the file ./production
[kvmHost]
stor ansible_host=<ip address of host>
dost ansible_host=<ip address of host>
[borg]
stor ansible_host=<ip address of host>
dost ansible_host=<ip address of host>
Install software playbook
This installs the necessary packages, sets up BorgBackup, and clones plus installs virtnbdbackup directly from its Git repository.
Create the file: playbooks/software_virtnbdbackup.yaml
---
- name: Install virtnbdbackup
hosts: kvmHost
become: true
tasks:
- name: install required packages
apt:
name:
- git
- qemu-utils
- libguestfs-tools
- python3
- python3-pip
- python3-libnbd
- python3-libvirt
- python3-lxml
- python3-tqdm
- python3-paramiko
- python3-lz4
- python3-colorlog
- nbd-client
- borgbackup
update_cache: yes
when: ansible_os_family == 'Debian'
- name: load NBD kernelmodul
modprobe:
name: nbd
state: present
params: max_part=8
- name: clone virtnbdbackup repository
git:
repo: "{{ virtnbdbackup_repo }}"
dest: "{{ virtnbdbackup_dir }}"
version: master
virtnbdbackup playbook
Create the file: playbooks/kvmVM_backup.yaml
---
- name: KVM VM Backup with virtnbdbackup and Borg
hosts: kvmHost
become: true
tasks:
- name: remove old backup
ansible.builtin.file:
path: "{{ backup_dir }}/{{ item }}_backup.qcow2"
state: absent
loop: "{{ vm_names }}"
- name: ensure backup folder exists
file:
path: "{{ backup_dir }}"
state: directory
mode: '0755'
- name: process virtnbdbackup for every VM
command:
argv:
- "{{ virtnbdbackup_dir }}/virtnbdbackup"
- -d
- "{{ item }}"
- -o
- "{{ backup_dir }}/{{ item }}_backup.qcow2"
loop: "{{ vm_names }}"
args:
executable: /bin/bash
- name: execute Borg-Backup
shell: |
borg create --compression {{ borg_compression }} {{ borg_repo }}::kvm-{{ ansible_date_time.iso8601_basic_short }} {{ backup_dir }}
register: borg_result
environment:
BORG_PASSPHRASE: "{{ borg_passphrase }}"
args:
executable: /bin/bash
- name: remove temporary virtnbdbackup files
file:
path: "{{ backup_dir }}"
state: absent
when: borg_result.rc == 0
- name: cleanup and recreate temporary backup folder
file:
path: "{{ backup_dir }}"
state: directory
mode: '0755'
when: borg_result.rc == 0
We need this uncompressed file because Borg requires it to efficiently scan and compress the final backup archive.
Create Host vars
For each host, we need a separate host_vars file where we define host-specific settings and list which VMs should be backed up.
Create the file: host_vars/dost.yaml
backup_dir: /var/backups
vm_names:
- node3
borg_repo: /srv/backup
borg_compression: zstd,3
vm_names : list of all VMs to backup
borg_repo : backup destination
And one for other Host
Create the file: host_vars/stor.yaml
backup_dir: /var/backups
vm_names:
- node2
- icinga2
- netbox
borg_repo: /tank/backup/stor
borg_compression: zstd,3
Install virtnbdbackup
only once needed
This playbook installs virtnbdbackup on all Hosts in group kvmHost
ansible-playbook -i production playbooks/software_virtnbdbackup.yaml --vault-password-file ~/.vault_pass.txt
Start backup
As the Ansible user created earlier, run the backup with:
ansible-playbook -i production --limit stor playbooks/kvmVM_backup.yaml --vault-password-file ~/.vault_pass.txt
That’s it — all wrapped up in a single command line, which I schedule as a crontab entry.
No worries about whether the machines are running or not — just reliable backups.
Restore
What’s a backup solution without a restore?
First, you need the Borg backup repository, then restore the content with:
List available backups:
sugras@stor:~$ sudo borg list --format '{archive}{NL}' /tank/backup/stor
[sudo] Passwort für sugras:
Enter passphrase for key /tank/backup/stor:
kvm-2025-07-09
kvm-20250709T191728
kvm-20250709T193031
kvm-20250709T220554
kvm-20250710T005033
kvm-20250718T215945
Pick one backup from the list and restore:
sudo borg extract /tank/backup/stor::kvm-20250718T215945 var/backups/icinga2_backup.qcow2 .
After typing the backup name, autocomplete helps, making it easy to restore a specific file.
In this example, I restore var/backups/icinga2_backup.qcow2 to my current working directory.
Now, use virtnbdrestore to extract the image:
/opt/virtnbdbackup/virtnbdrestore -i icinga2_backup.qcow2 -o dump
/opt/virtnbdbackup/virtnbdrestore -i icinga2_backup.qcow2 -o verify
/opt/virtnbdbackup/virtnbdrestore -i icinga2_backup.qcow2 -o ~/
Finally, copy or move the restored image to the desired location. It’s an extra step but safer, I think.
Hope this helps someone :-)
.