When running KVM guests with NAT networking (the default configuration in libvirt), your virtual machines are hidden behind the host's IP address. This creates a common pain point: how to expose specific services running on guests to external networks. The solution lies in proper port forwarding configuration.
For libvirt 0.8.3 and newer, we can leverage XML configuration to set up port forwarding rules. Here's how to implement it properly:
<network> <name>default</name> <forward mode='nat'> <nat> <port start='1024' end='65535'/> </nat> </forward> <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> <forwarder dev='eth0'/> </network>
To forward port 80 from host to guest:
<network> ... <ip address='192.168.122.1' netmask='255.255.255.0'> <tcp port='80' toPort='80' toAddr='192.168.122.50'/> </ip> ... </network>
1. Edit your network definition:
virsh net-edit default
2. Apply changes:
virsh net-destroy default virsh net-start default
After applying changes, verify with:
iptables -t nat -L -n -v
You should see rules like:
Chain PREROUTING (policy ACCEPT packets, 0 bytes) pkts bytes target prot opt in out source destination 0 0 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 to:192.168.122.50:80
For more complex scenarios, you might need direct iptables rules:
iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 192.168.122.50:80 iptables -I FORWARD -d 192.168.122.50/32 -p tcp -m state --state NEW -m tcp --dport 80 -j ACCEPT
To make iptables rules persistent across reboots on Ubuntu:
apt-get install iptables-persistent netfilter-persistent save
When working with KVM virtualization on Ubuntu using libvirt's NAT networking, you'll often need to expose guest services to external networks while having only one public IP address on the host. The default NAT configuration isolates guest VMs, requiring port forwarding rules to bridge this gap.
Libvirt 0.8.3+ introduced a more elegant way to handle port forwarding through network XML definitions. Here's the modern approach:
# First, find your virtual network name
virsh net-list --all
# Then dump its XML configuration
virsh net-dumpxml default > network.xml
Edit the network XML file to add forwarding rules. Here's a complete example forwarding host port 80 to guest port 80:
<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>
<portforward>
<protocol='tcp'>
<range start='80' end='80'/>
<localport start='80' end='80'/>
<guest ip='192.168.122.50'/>
</protocol>
</portforward>
</ip>
</network>
After modifying the XML, apply it with these commands:
# Destroy the current network (will disconnect VMs temporarily)
virsh net-destroy default
# Define the new configuration
virsh net-define network.xml
# Start the network again
virsh net-start default
# Set to autostart
virsh net-autostart default
For more complex scenarios, you might need to add iptables rules manually:
# Forward HTTP traffic
sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 192.168.122.50:80
sudo iptables -I FORWARD -d 192.168.122.50/32 -p tcp -m state --state NEW -m tcp --dport 80 -j ACCEPT
# Make rules persistent
sudo apt-get install iptables-persistent
sudo netfilter-persistent save
If forwarding isn't working:
- Verify the guest IP is correct in your rules
- Check that the guest firewall allows the forwarded ports
- Ensure the libvirt network is active (
virsh net-list
) - Confirm host firewall isn't blocking traffic (
sudo ufw status
)
For forwarding multiple ports to different guests:
<portforward>
<protocol='tcp'>
<range start='80' end='80'/>
<localport start='80' end='80'/>
<guest ip='192.168.122.50'/>
</protocol>
<protocol='tcp'>
<range start='2222' end='2222'/>
<localport start='22' end='22'/>
<guest ip='192.168.122.55'/>
</protocol>
</portforward>