One of the advantages of virtualization is the ability to clone virtual machines to quickly create homogeneous environments. However, when we clone a VM that contains an operating system, the clone inherits unique identifiers such as the machine ID, MAC addresses, host SSH keys, etc., which should be unique for each machine.

In this guide we will cover how to:

  • Clone virtual machines with virt-clone
  • Generalize clones with virt-sysprep
  • Customize new instances with virt-customize
  • Rename virtual machines with virsh domrename
  • Troubleshoot common issues after cloning

Prerequisites

Make sure the following packages are installed:

# Debian/Ubuntu
sudo apt install libguestfs-tools virtinst

# RHEL/Fedora/AlmaLinux
sudo dnf install libguestfs-tools virt-install

Key packages:

  • virt-clone: Tool for cloning virtual machines (included in virtinst)
  • virt-sysprep: Tool for generalizing VMs (included in libguestfs-tools)
  • virt-customize: Tool for customizing VMs without booting them (included in libguestfs-tools)

1. Prepare the base virtual machine

Before cloning, it’s advisable to prepare a reference machine with all the baseline configuration you need (installed packages, users, basic network configuration, etc.).

In our example, we’ll use a VM called debian13 as the base machine.

Shut down the virtual machine

Before cloning, we must shut it down:

virsh shutdown debian13

Verify that it is completely powered off:

virsh list --all

Expected output:

 Id   Name              State
----------------------------------
 -    debian13          shut off

2. Clone the virtual machine with virt-clone

virt-clone creates a full copy of a virtual machine, including its XML configuration and virtual disks.

Option 1: Automatic cloning (auto-generated disk name)

You can use --auto-clone so that virt-clone automatically generates the disk name by adding the -clone suffix:

virt-clone --original debian13 --name debian13-clone --auto-clone

Expected output:

Allocating 'debian13-clone.qcow2'    | 20 GB  00:00:05

Clone 'debian13-clone' created successfully.

Option 2: Specify a custom disk name

For more control over the disk name, use the --file parameter:

virt-clone --original debian13 \
	--name debian-template \
	--file /var/lib/libvirt/images/debian-template.qcow2

Expected output:

Allocating 'debian-template.qcow2'   | 20 GB  00:00:05

Clone 'debian-template' created successfully.

Clone multiple disks

If your VM has multiple disks, you can specify multiple --file parameters:

virt-clone --original debian13 \
	--name debian-template \
	--file /var/lib/libvirt/images/debian-template-disk1.qcow2 \
	--file /var/lib/libvirt/images/debian-template-disk2.qcow2

3. Rename virtual machines with virsh domrename

You can change a VM’s name easily with virsh domrename:

virsh domrename debian13-clone debian-base-image

Expected output:

Domain successfully renamed

Verify the change:

virsh list --all

Output:

 Id   Name                State
-----------------------------------
 -    debian13            shut off
 -    debian-base-image   shut off
 -    debian-template     shut off

Note: The virsh domrename command only changes the VM name in libvirt; it does not rename the disk file. If you also want to rename the disk, do it manually and then edit the XML configuration with virsh edit.


4. Generalize the VM with virt-sysprep

virt-sysprep removes machine-specific configurations to turn it into a reusable template. This tool is inspired by Microsoft Windows’ sysprep utility.

What does virt-sysprep do?

virt-sysprep performs multiple cleanup operations automatically:

  • Removes the machine-id and it will be regenerated on next boot
  • Deletes host SSH keys (they can be regenerated automatically or manually)
  • Clears the command history (bash_history)
  • Removes fixed MAC addresses
  • Deletes system logs
  • Cleans the package cache
  • Deletes temporary files
  • And much more…

Basic usage of virt-sysprep

sudo virt-sysprep -d debian-template

Output (excerpt):

[   0.0] Examining the guest ...
[   5.2] Performing "abrt-data" ...
[   5.2] Performing "backup-files" ...
[   5.3] Performing "bash-history" ...
[   5.3] Performing "machine-id" ...
[   5.3] Performing "net-hwaddr" ...
[   5.3] Performing "ssh-hostkeys" ...
[   5.4] Performing "tmp-files" ...
[   5.4] Performing "utmp" ...
[   5.5] Setting a random seed
[   5.5] Setting the machine ID in /etc/machine-id

virt-sysprep with additional options

You can set the hostname and the root password during generalization:

sudo virt-sysprep -d debian-template \
	--hostname debian-template \
	--root-password password:MySecurePassword123!

Useful virt-sysprep options

OptionDescription
--hostname NAMESets the host name
--root-password password:PASSSets the root password
--firstboot SCRIPTRuns a script on first boot
--operations LISTSpecifies which operations to perform (all by default)
--enable OPERATIONSEnables specific operations
--disable OPERATIONSDisables specific operations

Example disabling SSH host key removal:

sudo virt-sysprep -d debian-template --operations all,-ssh-hostkeys

5. Create new VMs from the template

Once you have a generalized machine image, you can quickly create multiple instances:

virt-clone --original debian-template \
	--name vm-debian-01 \
	--file /var/lib/libvirt/images/vm-debian-01.qcow2
virt-clone --original debian-template \
	--name vm-debian-02 \
	--file /var/lib/libvirt/images/vm-debian-02.qcow2

Verify the created VMs:

virsh list --all

6. Customize VMs with virt-customize

virt-customize lets you modify a VM without booting it, which is ideal for quickly customizing clones.

Basic example: Set hostname and password

sudo virt-customize -d vm-debian-01 \
	--hostname vm-debian-01 \
	--password usuario:password:MyPassword123!

Output:

[   0.0] Examining the guest ...
[   3.1] Setting a random seed
[   3.1] Setting the hostname: vm-debian-01
[   4.2] Setting passwords
[   5.0] Finishing off

Advanced options for virt-customize

sudo virt-customize -d vm-debian-02 \
	--hostname vm-debian-02 \
	--root-password password:RootPass123! \
	--password admin:password:AdminPass123! \
	--ssh-inject admin:file:/home/user/.ssh/id_rsa.pub \
	--run-command 'apt update && apt install -y nginx' \
	--timezone Europe/Madrid \
	--run-command 'systemctl enable nginx'

Useful options table

OptionDescription
--hostname NAMESets the hostname
--password USER:password:PASSSets a user’s password
--ssh-inject USER:file:KEY.pubInjects a public SSH key
--run-command 'COMMAND'Runs a command inside the VM
--install PACKAGESInstalls packages (comma-separated)
--timezone ZONESets the time zone
--updateUpdates all packages
--selinux-relabelRestores SELinux labels

Customization with a script

You can run a complete script:

sudo virt-customize -d vm-debian-01 \
	--upload /path/to/config.sh:/tmp/config.sh \
	--run-command 'bash /tmp/config.sh' \
	--delete /tmp/config.sh

7. Common problems and solutions

Problem 1: Error connecting via SSH - “Connection refused”

Cause: The host SSH keys were removed by virt-sysprep.

Symptoms: When checking the SSH service status inside the VM:

sudo systemctl status sshd

You will see errors such as:

sshd[387]: error: Could not load host key: /etc/ssh/ssh_host_rsa_key
sshd[387]: error: Could not load host key: /etc/ssh/ssh_host_ecdsa_key
sshd[387]: error: Could not load host key: /etc/ssh/ssh_host_ed25519_key
sshd[387]: fatal: No supported key exchange algorithms [preauth]

Solution 1: Regenerate all SSH keys automatically:

sudo ssh-keygen -A
sudo systemctl restart sshd

Output:

ssh-keygen: generating new host keys: RSA DSA ECDSA ED25519

Solution 2: Regenerate keys manually one by one:

sudo ssh-keygen -f /etc/ssh/ssh_host_rsa_key -N '' -t rsa
sudo ssh-keygen -f /etc/ssh/ssh_host_ecdsa_key -N '' -t ecdsa
sudo ssh-keygen -f /etc/ssh/ssh_host_ed25519_key -N '' -t ed25519
sudo systemctl restart sshd

Solution 3: Prevent virt-sysprep from deleting SSH keys:

sudo virt-sysprep -d debian-template --operations all,-ssh-hostkeys

Warning: If you keep the SSH keys in the template, all cloned VMs will share the same host keys, which is a security risk. Only do this in lab environments.

Problem 2: “WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!”

Cause: The host’s SSH keys changed and the client still has the old key in ~/.ssh/known_hosts.

Solution: Remove the old entry from the known_hosts file:

ssh-keygen -f "/home/usuario/.ssh/known_hosts" -R "192.168.122.50"

Or manually edit the ~/.ssh/known_hosts file and remove the corresponding line.

Problem 3: Duplicate MAC addresses

Cause: When cloning without virt-sysprep, VMs may have the same MAC address.

Solution: virt-clone automatically generates new MAC addresses. If you encounter issues, edit the VM:

virsh edit vm-debian-01

Find the <interface> section and remove or modify the <mac address='...'/> line. Libvirt will generate a new one on boot.

Problem 4: Duplicate machine-id

Cause: The /etc/machine-id file is identical across all cloned VMs.

Solution: virt-sysprep already takes care of this. If you did it manually, regenerate the machine-id:

sudo rm /etc/machine-id
sudo systemd-machine-id-setup

Here’s a complete workflow to create and deploy multiple VMs:

Step 1: Create and configure the base VM

# Create base VM (assuming you already have it)
virsh list --all

Step 2: Shut down and clone

virsh shutdown debian13
virt-clone --original debian13 \
	--name debian-template \
	--file /var/lib/libvirt/images/debian-template.qcow2

Step 3: Generalize the template

sudo virt-sysprep -d debian-template \
	--hostname debian-template \
	--root-password password:TemplatePass123!

Step 4: Create new instances

# Create VM 1
virt-clone --original debian-template \
	--name vm-web-01 \
	--file /var/lib/libvirt/images/vm-web-01.qcow2

# Customize VM 1
sudo virt-customize -d vm-web-01 \
	--hostname vm-web-01 \
	--password admin:password:AdminWeb01! \
	--ssh-inject admin:file:/home/user/.ssh/id_rsa.pub \
	--run-command 'apt update && apt install -y nginx'

# Create VM 2
virt-clone --original debian-template \
	--name vm-db-01 \
	--file /var/lib/libvirt/images/vm-db-01.qcow2

# Customize VM 2
sudo virt-customize -d vm-db-01 \
	--hostname vm-db-01 \
	--password admin:password:AdminDB01! \
	--ssh-inject admin:file:/home/user/.ssh/id_rsa.pub \
	--run-command 'apt update && apt install -y postgresql'

Step 5: Start and verify

virsh start vm-web-01
virsh start vm-db-01

# Check assigned IPs
virsh domifaddr vm-web-01
virsh domifaddr vm-db-01

# Connect via SSH
ssh admin@192.168.122.X

9. Summary of main commands

CommandDescription
virt-clone --original VM --name NEW --auto-cloneClone a VM with an automatic disk name
virt-clone --original VM --name NEW --file DISK.qcow2Clone specifying the disk name
virsh domrename OLD NEWRename a virtual machine
sudo virt-sysprep -d VMGeneralize a VM by removing unique identifiers
sudo virt-customize -d VM --hostname NAMECustomize a VM without booting it
sudo ssh-keygen -ARegenerate all SSH host keys
virsh edit VMEdit a VM’s XML configuration

References