Blog about software development


KVM port forwarding with UFW

27 Jan 2020 - by 'Maurits van der Schee'

In a previous post I have shown how to set up static IP addresses for virtual machines in KVM (on Ubuntu 18.04). I have also shown how to reconfigure the IP range of the KVM network. In this post I will show how to do port forwarding of specific traffic to your virtual machines without turning off UFW. This requires to reconfigure the KVM network to a "routed" network with explicit forwarding rules in iptables.

UFW and iptables chains

Before we start it is good to understand the relation between the 4 chains of iptables and UFW (Uncomplicated FireWall). Iptables has these 4 chains:

UFW only adjusts the "filter" chain. It does so by maintaining rules in "iptables-restore" format in the "/etc/ufw/" directory. The first file that is read by UFW is the file "/etc/ufw/before.rules". We will use this file to maintain the contents of the iptables "nat" chain.

Custom routing of the KVM network

We will change our network to type "route", so that there will be no (NAT) traffic flowing unless we explicitly specify it. Let's look again at our network configuration:

virsh net-edit default

And look for the line:

<forward mode='nat'/>

Change it into:

<forward mode='route'/>

Now we need to restart the network for this change to be effective:

virsh net-destroy default
virsh net-start default

Note that you need to shutdown all VMs and start all VMs again as the network restart breaks their connectivity.

Set up forwarding

Add the following lines to the end of the file "/etc/sysctl.conf" to make your Linux machine act as an IPv4 router:

# enable ipv4 routing
net.ipv4.ip_forward = 1

Now you need to run:

sudo sysctl -p

This command reloads sysctl config file and makes the settings effective.

Setting up UFW routing

To allow access to and from virtual machines we need the "ufw route" commands. Allow the KVM virtual machines to connect to the Internet, using:

sudo ufw route allow in on virbr0 out on eno1 from

To allow traffic from the Internet on our public interface to the specific subnet on our KVM bridge interface, execute:

sudo ufw route allow in on eno1 out on virbr0 to

Now you may still experience failing DNS (nslookup) and DHCP (dhclient) requests. To fix that run:

sudo ufw allow in on virbr0

This last command tells UFW to allow incomming traffic on the virbr0 (internal) interface.

The iptables nat chain

The iptables nat chain holds the effective NAT rules. We can let UFW flush the nat rules and set them again by modifying the file "/etc/ufw/before.rules". In this file add the following section to the top of the file (before the filter chain):

# Section to route the libvirt network
-A PREROUTING -i eno1 -p tcp -d --dport 80 -j DNAT --to-destination
-A PREROUTING -i eno1 -p tcp -d --dport 443 -j DNAT --to-destination

Note that "eno1" should be replaced by the public interface and "" by the public IP address. Also, the range "" should match your private network and "" the virtual machine handling the (web) traffic on TCP port 80 and 443 (HTTP and HTTPS).

Testing the forward

To execute the "before.rules" file and make these rules effective run:

sudo ufw reload

To see whether or not our "nat" iptables chain looks good we can run:

sudo iptables -t nat -L -v

It shows the current state (with some traffic counters, useful for debugging):

Chain PREROUTING (policy ACCEPT 10 packets, 600 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    4   240 DNAT       tcp  --  eno1   any     anywhere            tcp dpt:http to:
    6   360 DNAT       tcp  --  eno1   any     anywhere            tcp dpt:https to:

Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain POSTROUTING (policy ACCEPT 303 packets, 18196 bytes)
 pkts bytes target     prot opt in     out     source               destination         
  303 18196 MASQUERADE all  --  any    eno1        anywhere            

Now try connecting over HTTPS to the alternative (forwared) port:


This should now be responded to by the webserver on the virtual machine at

Next: Installing IPsec IKEv2 VPN

In the next post I will show you how to add an IPsec IKEv2 VPN to your KVM setup. I will show how to install the VPN endpoint on a virtual machine and make it available using port forwards. This allows for a simple networking setup and easy replacement of VPN technology.

Click here to read the next article (on how to install an IPsec IKEv2 VPN server on KVM).

PS: Liked this article? Please share it on Facebook, Twitter or LinkedIn.