Take snapshots and clone a virtual machine
Advanced
Tested setup: I ran every command in this guide top-to-bottom on a fresh Ubuntu 24.04 virtual machine (4 CPUs, 8 GB RAM) on 13 August 2026. The run continued directly from the end state of Create virtual machines from the terminal. Versions at test time: Ubuntu 24.04.4 LTS, libvirt 10.0.0, QEMU 8.2.2, virt-clone 4.1.0, virt-sysprep 1.52.0.
Found a problem? Tell me via the contact page.
Placeholders: anything written in CAPS-WITH-DASHES, like YOUR-VM-IP-HERE or YOUR-VM2-IP-HERE, is a value you replace with your own — here that means the addresses from your lease table. $USER is different — leave it exactly as written; the shell fills in your username for you.
What you'll end up with
Take a snapshot and restore it, then clone the VM. The clone test demonstrates conflicting machine identities and the steps used to change them.
The finished state from my test run — two independent machines, born a few minutes apart from one image:
$ virsh net-dhcp-leases default
Expiry Time MAC address Protocol IP address Hostname Client ID or DUID
------------------------------------------------------------------------------------------------------
2026-08-13 18:03:49 52:54:00:04:09:f8 ipv4 192.168.122.82/24 vm1 ff:56:...:20:99
2026-08-13 18:15:37 52:54:00:42:32:0e ipv4 192.168.122.153/24 vm2 ff:56:...:dd:d0
Who this is for
Advanced. You have done Create virtual machines from the terminal and kept its end state: vm1 running, reachable over SSH with the vm-lab-key. This guide starts exactly there.
Time and cost
The commands for this guide and the headless guide took me about 7 minutes of measured run time in total, as one continuous run. Budget 45 minutes for this half. Cost: nothing; every tool is in Ubuntu's archive.
The test host was itself a VM, so the guests used nested virtualisation. Timings depend on the host hardware, storage and network.
Words you'll meet
- Internal snapshot — a saved moment (disk, and RAM if running) stored inside the qcow2 file. There are also external snapshots, discussed below. (basics)
- machine-id — a unique identity string every Linux install carries in
/etc/machine-id. Clones inherit it, which causes real trouble. (basics) - DUID — the identifier a machine presents to DHCP. On Ubuntu it is derived from the machine-id — which is why cloned machine-ids poison DHCP. (basics)
- DHCP lease — the network's record of "this machine has this IP".
virsh net-dhcp-leasesis how you find a headless guest. (basics)
Before you start
- The end state of Create virtual machines from the terminal —
vm1running, the~/.ssh/vm-lab-keypair, and the libvirt group membership. - Around 4 GB free disk and 4 GB free RAM while both guests run.
The steps
Step 1 — snapshots: save a moment, ruin things, rewind
Libvirt supports two snapshot styles.
Internal snapshots live inside the qcow2 file: one file to manage, one command each way, and they can include RAM while the guest runs. The trade-offs: qcow2-only, slower on big disks, and some tooling (block commit, incremental backup workflows) does not support them.
External snapshots write changes to a new overlay file. Faster and more flexible — but you manage a chain of files, and merging them back is on you.
This guide uses internal snapshots for a small practice VM. It does not cover external-snapshot cleanup or production backup design.
Take a snapshot of the running guest — RAM included:
virsh snapshot-create-as vm1 clean-state --description "Fresh vm1 before experiments"
The important line is:
Domain snapshot clean-state created
Two seconds, in my test — the guest never stopped running.
Now damage the future: create a file that the snapshot knows nothing about (from the host, in one line):
ssh -i ~/.ssh/vm-lab-key ubuntu@YOUR-VM-IP-HERE "echo do-not-lose-me > ~/precious.txt; ls -l ~/precious.txt"
Your output will vary, but look for:
-rw-rw-r-- 1 ubuntu ubuntu 15 Aug 13 17:02 /home/ubuntu/precious.txt
List snapshots:
virsh snapshot-list vm1
You should see something like:
Name Creation Time State
----------------------------------------------------
clean-state 2026-08-13 17:02:18 +0000 running
Rewind:
virsh snapshot-revert vm1 clean-state
A passing run includes:
Domain snapshot clean-state reverted
Three seconds, and the guest is running again immediately — reverting to a running snapshot restores the RAM too, no reboot.
Check it worked. The file made after the snapshot must be gone:
ssh -i ~/.ssh/vm-lab-key ubuntu@YOUR-VM-IP-HERE "ls -l ~/precious.txt"
You should see something like:
ls: cannot access '/home/ubuntu/precious.txt': No such file or directory
That error is the expected result. Time travel, demonstrated. And the obvious warning made explicit: everything after a snapshot dies when you revert — snapshots are a lab tool and a pre-upgrade parachute, not a backup.
Step 2 — clone it, and watch the clone break
You want a second VM. Copying feels free — one command, no download. Do it, and then watch closely, because what happens next is the most valuable part of this guide.
Know the shape of the problem before you meet it. A clone is a full copy — including its identity. Making it a genuinely new machine means fixing four things: the machine-id, the netplan MAC pin, the hostname, and the SSH host keys. This step springs the trap; the next step fixes all four.
virt-clone needs the source shut off:
virsh shutdown vm1
Clone (8 seconds in my test):
virt-clone --original vm1 --name vm2 --auto-clone
The important line is:
Clone 'vm2' created successfully.
Two notes from my test before we boot anything. The snapshot did not come along — virsh snapshot-list vm2 is empty. And the seed CD was not copied — vm2 points at vm1's seed file (it is read-only, so virt-clone shares it). Both matter later.
Boot both:
virsh start vm1
virsh start vm2
In the recorded test, vm1 returned on its previous address, but vm2 did not appear in the network lease list:
virsh net-dhcp-leases default
You should see something like:
Expiry Time MAC address Protocol IP address Hostname Client ID or DUID
------------------------------------------------------------------------------------------------------
2026-08-13 18:03:49 52:54:00:04:09:f8 ipv4 192.168.122.82/24 vm1 ff:56:...:20:99
One lease. I waited over four minutes in testing — vm2 never asked for an address at all.
Here is the diagnosis, exactly as I ran it. Read the clone's inherited network config through vm1 (they are identical clones, after all):
ssh -i ~/.ssh/vm-lab-key ubuntu@YOUR-VM-IP-HERE "sudo cat /etc/netplan/50-cloud-init.yaml"
Your output will vary, but look for:
network:
version: 2
ethernets:
enp1s0:
match:
macaddress: "52:54:00:04:09:f8"
dhcp4: true
There it is. Cloud-init pinned the network card by MAC address on first boot. virt-clone — correctly — gave vm2 a fresh MAC:
virsh domiflist vm2
Your output will vary, but look for:
Interface Type Source Model MAC
-------------------------------------------------------------
vnet4 network default virtio 52:54:00:42:32:0e
Different MAC. The clone's netplan matches no interface, so it configures nothing — it never even asks for DHCP.
And lurking one layer down: the clone also inherited vm1's /etc/machine-id, from which Ubuntu derives its DHCP DUID. Even with netplan fixed, the two machines would present the same identity to the DHCP server. A clone is not a new machine until you make it one.
Step 3 — give the clone its own identity
Shut the clone down; we operate on its disk from outside:
virsh shutdown vm2
Install libguestfs — the toolkit for editing guest disks without booting them (110 packages, 42 seconds in my test):
sudo apt-get install -y libguestfs-tools
Eject the seed CD it shares with vm1 — the clone must not re-read vm1's first-boot config:
virsh change-media vm2 sda --eject --config
The important line is:
Successfully ejected media.
Reset the machine-id and set the hostname. One deliberate flag choice here: --enable machine-id,customize runs only those operations. virt-sysprep's default operation set also wipes authorized_keys — which would lock us out of our own clone:
sudo virt-sysprep -d vm2 --enable machine-id,customize --hostname vm2
A passing run includes:
[ 0.0] Examining the guest ...
[ 8.1] Performing "machine-id" ...
[ 8.1] Performing "customize" ...
[ 8.2] Setting the machine ID in /etc/machine-id
[ 8.2] Setting the hostname: vm2
Un-pin the network card — rewrite the clone's netplan to plain "DHCP on enp1s0, any MAC":
sudo virt-customize -d vm2 --run-command "printf 'network:\n version: 2\n ethernets:\n enp1s0:\n dhcp4: true\n' > /etc/netplan/50-cloud-init.yaml && chmod 600 /etc/netplan/50-cloud-init.yaml"
A passing run includes:
[ 0.0] Examining the guest ...
[ 19.8] Running: printf [...snipped...]
[ 19.9] Finishing off
Boot it:
virsh start vm2
Check it worked. 34 seconds later in my test:
virsh net-dhcp-leases default
You should see something like:
Expiry Time MAC address Protocol IP address Hostname Client ID or DUID
------------------------------------------------------------------------------------------------------
2026-08-13 18:03:49 52:54:00:04:09:f8 ipv4 192.168.122.82/24 vm1 ff:56:...:20:99
2026-08-13 18:15:37 52:54:00:42:32:0e ipv4 192.168.122.153/24 vm2 ff:56:...:dd:d0
Two leases, two hostnames, two different DUIDs — that last column differing is the machine-id reset doing its job. Prove it end to end:
ssh -i ~/.ssh/vm-lab-key ubuntu@YOUR-VM2-IP-HERE "hostname; cat /etc/machine-id"
Your output will vary, but look for:
vm2
2336d8cbf5e0a8015956a0b344b0a4ea
vm1 reported b93b68cff1c749298b89ce14210e5df1 in my test — different identity, as required.
One last inherited secret: the clone still carries vm1's SSH host keys (I checksummed them — byte-identical). For a disposable lab this is acceptable; for anything real, regenerate them — otherwise two servers claim the same cryptographic identity. Regenerate inside vm2:
ssh -i ~/.ssh/vm-lab-key ubuntu@YOUR-VM2-IP-HERE "sudo rm /etc/ssh/ssh_host_*; sudo dpkg-reconfigure -f noninteractive openssh-server; sudo systemctl restart ssh; sudo md5sum /etc/ssh/ssh_host_ed25519_key.pub"
You should see something like:
4bfddc6cc86a97a47acccf89392dde2d /etc/ssh/ssh_host_ed25519_key.pub
Different checksum from vm1's (mine was a36e8aa9...). Now they are genuinely two machines.
Something went wrong?
Everything below happened in my recorded run — most of it on purpose.
- Your clone is
runningbut never gets an IP → it means netplan is pinned to the original's MAC (and the machine-id is cloned underneath) → this is Steps 2-3's whole story; apply Step 3. - You see
error: ... cannot delete inactive domain with 1 snapshotswhen undefining → it means snapshot metadata blocks the delete → add--snapshots-metadata(the headless guide's Undo shows it).
Undo all of this
Tested setup — as one combined undo with Create virtual machines from the terminal. Here is the part that removes what this guide added; that guide's undo then clears vm1, the packages, and the folders.
Force the clone off and delete it:
virsh destroy vm2
virsh undefine vm2 --remove-all-storage
(No snapshots on vm2, so no extra flag needed.)
Two things this guide added live on vm1's side, and the headless guide's undo handles both. The clean-state snapshot goes when vm1 is undefined — that is why its undo carries --snapshots-metadata; I captured the refusal without it. And libguestfs-tools, installed here in Step 3, is included in that guide's package purge.
Check it worked.
virsh list --all
Only vm1 remains in the list. Then run Create virtual machines from the terminal's Undo; I ran the two back-to-back as one sequence and ended with a completely clean machine.
Where to go next
- The clone workflow you just learned is the seed of a real home lab: one golden image, sysprep, many guests. Pair it with Back up and restore files with restic before any guest holds data you care about — snapshots are not backups.
- Create virtual machines from the terminal — the first half of this pair, if you landed here directly.
- Official docs: Ubuntu Server virtualisation documentation — plus
man virt-sysprepfor the full operations list (worth a read before trusting defaults).
Last tested: 13 August 2026 on Ubuntu 24.04.4 LTS. Versions: libvirt 10.0.0, QEMU 8.2.2, virt-clone 4.1.0, libguestfs/virt-sysprep 1.52.0. Tested as one continuous run with the headless VMs guide; transcripts live in ../guide-09-headless-vms/.