When using virt-manager on Arch Linux, virtual machines may successfully obtain IP addresses from the NAT network (typically 192.168.122.x) but fail to access the internet. This manifests as:
- ✅ VM gets IP address from DHCP (e.g., 192.168.122.112/24)
- ✅ VM can ping the gateway (192.168.122.1)
- ✅ DNS resolution works inside the VM
- ❌ VM cannot reach external IP addresses (e.g., 8.8.8.8)
- ❌ Web browsing and external connectivity fails
The issue occurs because libvirt fails to automatically create the necessary iptables NAT rules for the virtual network. This can happen due to:
- Docker interference: Docker aggressively manages iptables rules and may conflict with libvirt's rule creation
- Service startup timing: libvirt network starting before proper iptables initialization
- Missing or incorrect FORWARD chain rules: Blocking rules processed before allowing rules
- NAT MASQUERADE rules not created: No network address translation for VM traffic
- Host OS: Arch Linux
- Virtualization: libvirt + virt-manager
- Network Configuration: NAT network (virbr0 bridge)
- VM Network: 192.168.122.0/24 subnet
- Gateway: 192.168.122.1
# Check host network interfaces
ip -c addr show
# Check VM IP configuration (inside VM)
ip addr show
ip route showExpected output shows:
- Host has
virbr0bridge at 192.168.122.1/24 - VM has IP in 192.168.122.x range
- VM default route points to 192.168.122.1
# From inside VM - test gateway connectivity
ping -c 4 192.168.122.1
# Test external IP (this will fail)
ping -c 4 8.8.8.8
# Test DNS resolution
nslookup google.com# Verify IP forwarding is enabled
cat /proc/sys/net/ipv4/ip_forward
# Check existing NAT rules
sudo iptables -t nat -L POSTROUTING -n -v
# Look for libvirt rules (likely missing)
sudo iptables -t nat -S | grep 192.168.122# Check network status
sudo virsh net-list --all
# Verify network configuration
sudo virsh net-dumpxml default# Enable IP forwarding (temporary)
sudo sysctl net.ipv4.ip_forward=1
# Make permanent
echo 'net.ipv4.ip_forward=1' | sudo tee -a /etc/sysctl.conf# Restart the default network
sudo virsh net-destroy default
sudo virsh net-start default
# Verify it's running
sudo virsh net-list# Clear any conflicting FORWARD rules
sudo iptables -F FORWARD
# Add NAT MASQUERADE rule for VM network
sudo iptables -t nat -A POSTROUTING -s 192.168.122.0/24 ! -d 192.168.122.0/24 -j MASQUERADE
# Add FORWARD rules (in correct order - ACCEPT before REJECT)
sudo iptables -I FORWARD 1 -d 192.168.122.0/24 -o virbr0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
sudo iptables -I FORWARD 2 -s 192.168.122.0/24 -i virbr0 -j ACCEPT
sudo iptables -I FORWARD 3 -i virbr0 -o virbr0 -j ACCEPT
# Restart Docker to restore its rules
sudo systemctl restart docker# Check NAT rules
sudo iptables -t nat -L POSTROUTING -n -v | grep 192.168.122
# Check FORWARD rules
sudo iptables -L FORWARD -n -v
# Ensure FORWARD policy allows traffic
sudo iptables -P FORWARD ACCEPTFrom inside the VM:
# Test external IP connectivity
ping -c 4 8.8.8.8
# Test web connectivity
curl -I google.com
# Should now work successfullyThe manual iptables rules will be lost after reboot. To make them persistent:
# Save current iptables rules
sudo iptables-save | sudo tee /etc/iptables/iptables.rules
# Enable iptables service to restore rules on boot
sudo systemctl enable iptables.service
sudo systemctl start iptables.service
# Ensure libvirt network autostarts
sudo virsh net-autostart default# Check that services are enabled
sudo systemctl is-enabled libvirtd
sudo systemctl is-enabled iptables
# Verify network autostart
sudo virsh net-list --autostartChain POSTROUTING (policy ACCEPT)
MASQUERADE all -- * * 192.168.122.0/24 !192.168.122.0/24
MASQUERADE all -- * !docker0 172.17.0.0/16 0.0.0.0/0
Chain FORWARD (policy ACCEPT)
ACCEPT all -- * virbr0 0.0.0.0/0 192.168.122.0/24 ctstate RELATED,ESTABLISHED
ACCEPT all -- virbr0 * 192.168.122.0/24 0.0.0.0/0
ACCEPT all -- virbr0 virbr0 0.0.0.0/0 0.0.0.0/0
- Check rule order: ACCEPT rules must come before REJECT rules in FORWARD chain
- Verify IP forwarding:
cat /proc/sys/net/ipv4/ip_forwardshould return1 - Check host connectivity: Ensure host can reach internet
- Review logs:
sudo journalctl -u libvirtd -f
- Verify iptables service:
sudo systemctl status iptables - Check saved rules:
cat /etc/iptables/iptables.rules - Manual restore:
sudo iptables-restore < /etc/iptables/iptables.rules
If Docker is interfering:
# Stop Docker temporarily
sudo systemctl stop docker
# Restart libvirt
sudo systemctl restart libvirtd
sudo virsh net-start default
# Check if libvirt creates rules automatically
sudo iptables -t nat -L | grep 192.168.122To avoid this issue on new Arch Linux installations:
-
Install in correct order:
sudo pacman -S libvirt virt-manager sudo systemctl enable libvirtd -
Configure before starting VMs:
echo 'net.ipv4.ip_forward=1' | sudo tee -a /etc/sysctl.conf sudo systemctl enable iptables
-
Add user to libvirt group:
sudo usermod -a -G libvirt $USER
This issue is a common problem on Arch Linux where libvirt's automatic iptables rule creation fails, often due to Docker interference or service timing issues. The solution involves manually creating the necessary NAT and FORWARD rules and making them persistent through the iptables service.
The key components of the fix are:
- ✅ IP forwarding enabled on host
- ✅ NAT MASQUERADE rule for VM subnet
- ✅ FORWARD chain rules allowing VM traffic
- ✅ Proper rule ordering (ACCEPT before REJECT)
- ✅ Persistence through iptables service
After applying this fix, virtual machines will have full internet connectivity that persists across reboots.
Author: drewdomi
Date: 2025-10-03
Environment: Arch Linux with libvirt/virt-manager
Issue: VM NAT network internet connectivity
Status: ✅ Resolved