Virtualization

KVM Networking: Custom NAT Network with Static DHCP

Create a persistent libvirt NAT network, reserve a predictable DHCP address, attach a second virtio NIC, configure Netplan, and control route priority.

By InventiveHQ Team

Part of the InventiveHQ KVM virtualization series. This lesson reuses demo-ubuntu01 from 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

ResourceValue
Networktutorial-net
ModeNAT
Host bridgevirbr150
Subnet192.168.150.0/24
Gateway192.168.150.1
Dynamic range192.168.150.100–192.168.150.200
Reserved guest address192.168.150.10
Guest MAC52:54:00:77:05:01
Guest interfaceenp8s0 in the validated VM
Secondary route metric200

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:

  • enp8s0 received 192.168.150.10/24.
  • The original 192.168.122.0/24 interface kept default-route metric 100.
  • The new 192.168.150.0/24 interface used metric 200.
  • ip route get 1.1.1.1 selected 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

Return to the KVM virtualization series index for the planned snapshot, backup, cloning, passthrough, performance, and migration lessons.

Frequently Asked Questions

How do I create a custom NAT network in libvirt?

Write a network XML definition containing a name, forward mode nat, unique bridge, private IP subnet, and optional DHCP range. Define it with virsh net-define, start it, enable autostart, and verify its generated XML and host bridge before attaching guests.

How do I give a KVM guest a predictable DHCP address?

Add a DHCP host entry to the libvirt network XML that maps a stable guest MAC address to the desired address and name. Attach the NIC with that same explicit MAC. The guest must still run DHCP on the interface before the lease appears.

Why did the new KVM interface have no IP address?

Hot-plugging a virtual NIC adds hardware but does not automatically change the guest operating system's network policy. The Ubuntu guest in this lab required a Netplan stanza enabling DHCP on the new interface.

Why did attaching a second DHCP interface add another default route?

A DHCP server can advertise a gateway, so each DHCP interface may install a default route. Use Netplan dhcp4-overrides route-metric to give the secondary path a higher metric and preserve the intended primary route.

What is the difference between libvirt NAT, isolated, and bridged networks?

A NAT network gives guests private addressing and outbound connectivity through the host. An isolated network omits forwarding and connects only its guests and host bridge. A bridged interface places the guest on an external Layer 2 network and requires host and physical-network planning.

Need help from an IT & cybersecurity partner?

InventiveHQ helps businesses secure, modernize, and run their technology. Let's talk about your goals.

Get in touch