libvirt Networking: NAT vs Bridged vs Others
Introduction¶
libvirt offers several ways to connect a VM's virtual NIC to the outside world. The choice comes down to one question: do other machines need to reach the VM directly?
NAT (the default default network)¶
<network>
<name>default</name>
<forward mode='nat'/>
<bridge name='virbr0' stp='on' delay='0'/>
<ip address='192.168.122.1' netmask='255.255.255.0'>
<dhcp><range start='192.168.122.2' end='192.168.122.254'/></dhcp>
</ip>
</network>
- libvirt creates
virbr0, runs dnsmasq on it for DHCP + DNS, and adds iptables/nftables MASQUERADE rules. - VMs get
192.168.122.x, reach the internet via the host's IP. - Nothing outside the host can initiate a connection to the VM (without port forwarding).
- Great for: dev laptops, throwaway VMs, anything that only makes outbound connections.
Port-forward a service in with a hook or virsh-managed rule if you must, but
that's a sign you want bridged.
Bridged (VMs on the LAN)¶
The VM's NIC joins a Linux bridge that also contains the host's physical NIC. The VM is a first-class citizen on your LAN — it gets an IP from the LAN's DHCP, and anything on the LAN can reach it.
Create the bridge (persistently — do it on the console, you'll drop SSH):
# NetworkManager (RHEL/Ubuntu)
nmcli con add type bridge con-name br0 ifname br0
nmcli con add type ethernet con-name br0-port ifname eth0 master br0
nmcli con mod br0 ipv4.method manual ipv4.addresses 192.168.1.10/24 \
ipv4.gateway 192.168.1.1 ipv4.dns 192.168.1.1
nmcli con up br0
Then a libvirt network (or just reference the bridge directly):
<network>
<name>lan</name>
<forward mode='bridge'/>
<bridge name='br0'/>
</network>
<interface type='bridge'>
<source bridge='br0'/>
<model type='virtio'/>
</interface>
- Use for: servers, anything that needs a stable reachable address, VMs that peers connect to.
- Caveat: Wi-Fi usually can't be bridged (APs drop frames with a MAC other than the associated one) — use routed mode or macvtap on wireless.
Routed¶
<forward mode='route'/>
Like NAT but without the address translation — the host routes between the VM subnet and the LAN. The VM's real (private) subnet IP is visible, but the rest of the LAN needs a route to that subnet pointing at the host (usually a static route on the router). Niche; use when you want isolation of the VM subnet but full reachability.
Isolated¶
<network><name>isolated</name><bridge name='virbr1'/>
<ip address='10.10.0.1' netmask='255.255.255.0'>
<dhcp><range start='10.10.0.2' end='10.10.0.254'/></dhcp>
</ip>
</network>
No <forward> — VMs on this network talk to each other and the host, but have
no route out. For lab environments, a DB tier that shouldn't face the
internet, etc.
macvtap¶
Attaches the VM directly to a physical NIC with its own MAC, bypassing a bridge:
<interface type='direct'>
<source dev='eth0' mode='bridge'/>
<model type='virtio'/>
</interface>
Slightly lower overhead than a bridge. Big gotcha: in the default bridge
(vepa-ish) mode, the VM cannot talk to its own host — the traffic goes out
the NIC and the host's stack never sees it. Fine if the host is purely a
hypervisor; annoying otherwise.
Verification and troubleshooting¶
virsh net-list --all
virsh net-dumpxml default
brctl show # or: bridge link
ip -br addr
virsh domifaddr myvm # VM's leased IP (NAT/managed networks)
- VM has no network — the libvirt network isn't started
(
virsh net-start default), or (bridge) the bridge doesn't exist / the physical NIC isn't enslaved (bridge link). - NAT VM can't reach internet — host IP forwarding off
(
sysctl net.ipv4.ip_forward→ set 1), firewalld/nftables blocking the forward chain, or afirewalldzone change removed libvirt's rules (systemctl restart libvirtd). - Bridged VM gets no DHCP — the LAN DHCP isn't reaching it: the host bridge
has STP delay (set
stp offorforward-delay 0), or a switch port with port-security / one-MAC-per-port. - Bridged VM works but host↔VM fails — you used macvtap, not a real bridge. Switch to a Linux bridge, or add a macvtap interface on the host too.
- Wi-Fi + bridge = nothing works — expected. Use routed mode or macvtap with
mode='bridge'on the wireless NIC (and accept host↔VM limitation), or NAT. virbr0still there after deleting the network —virsh net-undefine defaultand it's gone on next libvirtd restart; the interface lingers until then.
Related tools and reading¶
- On-site: Subnet Calculator, Port Checker.
- Related posts: KVM and libvirt: your first VM, nmcli: common tasks without the GUI.
Stuck on something this site can't fix?Reach out to Prabath directly on LinkedIn.More in Virtualization
virtio Drivers Explained
Emulating a real NIC or disk controller is slow. virtio is a paravirtual interface the guest talks to directly — here's what each virtio device does and how to enable it.
October 24, 2026cloud-init for VM Templates
Build one generic image, then let cloud-init set the hostname, users, SSH keys, packages, and network per instance at first boot. Here's the NoCloud workflow for KVM.
October 24, 2026GPU Passthrough with VFIO
Passing a physical GPU to a VM needs IOMMU, a clean IOMMU group, vfio-pci binding, and often a vBIOS. Here's the path and the notorious failure modes.
October 24, 2026