Part of the InventiveHQ KVM virtualization series. This lesson reuses
demo-ubuntu01from Episodes 2–4.
The default libvirt network is excellent for getting started, but real labs often need separate application tiers, predictable addresses, or a place to test routing and firewall policy without changing the physical LAN.
This episode creates tutorial-net, a persistent private NAT network on 192.168.150.0/24. It reserves 192.168.150.10 for a known MAC, hot-plugs a second virtio NIC into the running guest, enables DHCP with Netplan, and assigns the new route a higher metric so the original interface remains preferred.
Lab verified July 2026 · fixed DHCP lease · live and persistent NIC · Netplan route metrics
Use the command builder to inspect networks and domain interfaces before changing a guest's connectivity.
Finished network layout
| Resource | Value |
|---|---|
| Network | tutorial-net |
| Mode | NAT |
| Host bridge | virbr150 |
| Subnet | 192.168.150.0/24 |
| Gateway | 192.168.150.1 |
| Dynamic range | 192.168.150.100–192.168.150.200 |
| Reserved guest address | 192.168.150.10 |
| Guest MAC | 52:54:00:77:05:01 |
| Guest interface | enp8s0 in the validated VM |
| Secondary route metric | 200 |
Choose a subnet and bridge name that do not overlap existing host, VPN, container, physical, or libvirt networks.
1. Inventory the current network state
On the KVM host:
export LIBVIRT_DEFAULT_URI=qemu:///system
VM=demo-ubuntu01
virsh uri
virsh net-list --all
virsh domiflist "$VM"
ip -brief address
ip route
Confirm the planned name and MAC are unused:
virsh net-info tutorial-net 2>/dev/null && {
echo "tutorial-net already exists; inspect it first"
exit 1
}
virsh dumpxml "$VM" | grep -i '52:54:00:77:05:01' && {
echo "planned MAC is already attached"
exit 1
}
2. Define the custom NAT network
Create tutorial-net.xml:
<network>
<name>tutorial-net</name>
<forward mode='nat'/>
<bridge name='virbr150' stp='on' delay='0'/>
<domain name='tutorial.test'/>
<ip address='192.168.150.1' netmask='255.255.255.0'>
<dhcp>
<range start='192.168.150.100' end='192.168.150.200'/>
<host mac='52:54:00:77:05:01'
name='demo-ubuntu01-data'
ip='192.168.150.10'/>
</dhcp>
</ip>
</network>
The fixed address sits outside the dynamic range, preventing the general pool from leasing it to a different MAC.
Omit <forward mode='nat'/> when you intentionally want an isolated network with no forwarding beyond the host bridge.
3. Define, start, and persist the network
virsh net-define tutorial-net.xml
virsh net-start tutorial-net
virsh net-autostart tutorial-net
virsh net-info tutorial-net
virsh net-dumpxml tutorial-net
ip -brief address show virbr150
The validated network reported active, persistent, and autostarting, with virbr150 as its bridge.
Libvirt may add generated details such as a network UUID, bridge MAC, and NAT port range when the definition is active. Inspect the resulting XML rather than expecting byte-for-byte output identical to the input.
4. Attach a second NIC to the running guest
Attach an explicitly addressed virtio interface:
virsh attach-interface \
"$VM" \
network \
tutorial-net \
--model virtio \
--mac 52:54:00:77:05:01 \
--live \
--config
Verify both live and persistent state:
virsh domiflist "$VM"
virsh dumpxml "$VM" --inactive > "${VM}-two-networks.xml"
virsh net-dhcp-leases tutorial-net
virsh domifaddr "$VM" --source agent
Immediately after hot-plug, the lab showed the new interface in domiflist but no DHCP lease or IPv4 address. That is expected when the guest has not been told to use the new hardware.
5. Discover the interface name inside Ubuntu
Inside the guest:
ip -brief link
ip -brief address
Match the interface to the MAC address rather than copying a device name blindly:
for nic in /sys/class/net/*; do
printf '%s ' "$(basename "$nic")"
cat "$nic/address"
done
The validated guest named the interface enp8s0. Predictable interface names can differ when virtual PCI topology differs.
6. Enable DHCP with a controlled route metric
Create /etc/netplan/60-tutorial-net.yaml using the interface name discovered above:
network:
version: 2
ethernets:
enp8s0:
dhcp4: true
dhcp4-overrides:
route-metric: 200
Install and validate the configuration:
sudo chmod 600 /etc/netplan/60-tutorial-net.yaml
sudo netplan generate
sudo netplan apply
Keep serial-console access available while changing networking. A YAML error or route change can interrupt SSH.
7. Verify the lease, addresses, and routes
Inside the guest:
ip -brief address show enp8s0
ip route show default
ip route get 1.1.1.1
ping -c 2 -I 192.168.150.10 192.168.150.1
The verified result was:
enp8s0received192.168.150.10/24.- The original
192.168.122.0/24interface kept default-route metric 100. - The new
192.168.150.0/24interface used metric 200. ip route get 1.1.1.1selected the original interface.- The guest reached the custom gateway at
192.168.150.1.
Back on the host:
virsh net-dhcp-leases tutorial-net
virsh domifaddr "$VM" --source agent
virsh domiflist "$VM"
The lease table mapped 52:54:00:77:05:01 to 192.168.150.10/24 with hostname demo-ubuntu01-data, and the guest agent reported both interfaces.
Why the route metric matters
Both libvirt NAT networks advertise a gateway through DHCP. Without an override, the guest initially installed two default routes with equal metric 100. Equal-cost defaults can make path selection and troubleshooting less obvious.
Netplan's dhcp4-overrides.route-metric assigns the secondary interface a higher cost. Lower metrics are preferred, so 100 remains the primary path and 200 is secondary.
If the custom network should never install a default route, consider use-routes: false instead. That changes behavior more substantially, so choose based on the intended network design rather than copying it reflexively.
Safe detach and network cleanup
Remove guest configuration before detaching hardware:
# Inside the guest
sudo rm /etc/netplan/60-tutorial-net.yaml
sudo netplan apply
# On the KVM host
virsh detach-interface \
demo-ubuntu01 \
network \
--mac 52:54:00:77:05:01 \
--live \
--config
Remove the network only after no domain uses it:
virsh net-dhcp-leases tutorial-net
virsh net-destroy tutorial-net
virsh net-undefine tutorial-net
Do not delete a bridge or network merely because one guest stopped using it. Inventory every dependent domain first.
Troubleshooting
The second interface exists but has no address
Check the guest interface name, Netplan YAML, and DHCP service:
ip -brief link
sudo netplan generate
networkctl status enp8s0
virsh net-dhcp-leases tutorial-net
The reserved address was not assigned
Confirm that the MAC in domiflist exactly matches the network's DHCP host entry. Then renew DHCP or reapply Netplan.
SSH started using the wrong interface
Inspect all default routes and metrics:
ip route show default
ip route get YOUR_ADMIN_ADDRESS
Use the serial console before applying corrective route changes remotely.
The network will not start
Check for overlapping subnets, an existing bridge named virbr150, XML errors, and host firewall conflicts:
virsh net-info tutorial-net
virsh net-dumpxml tutorial-net
ip address
ip route
Official references
- libvirt network XML format
- virsh virtual-network commands
- Netplan DHCP and route-metric examples
- Netplan YAML reference
- Ubuntu Server libvirt networking
Return to the KVM virtualization series index for the planned snapshot, backup, cloning, passthrough, performance, and migration lessons.