The Limitation of Simple Firewalls
In standard Ubuntu deployments, administrators rely on ufw (Uncomplicated Firewall) to secure their servers. ufw is fantastic for simple tasks; typing sudo ufw allow 22/tcp is infinitely easier than writing complex iptables rules.
However, ufw is designed to protect the local machine. What if your Ubuntu server is not just a web server, but is acting as the primary router for a private internal network? If you have a private database server on 10.0.0.5 that needs to download updates from the internet, but it has no public IP address, the Ubuntu server must perform Network Address Translation (NAT) (specifically Masquerading) to route the traffic.
Conversely, if an external user needs to access that internal database, the Ubuntu server must perform Port Forwarding, intercepting traffic on its public IP and rewriting the packet destination to the internal server.
While ufw obscures this complexity, you can configure it to act as a highly advanced NAT router by directly editing its underlying configuration files.
Step 1: Enabling IP Forwarding in the Kernel
By default, the Linux kernel is explicitly programmed to drop any packet it receives that is not destined for its own IP address. To act as a router, you must instruct the kernel to forward packets between its network interfaces.
Open the primary kernel parameter configuration file:
sudo nano /etc/sysctl.conf
Uncomment (remove the #) from the following line to enable IPv4 forwarding:
net.ipv4.ip_forward=1
Save the file and forcefully apply the new kernel parameter:
sudo sysctl -p
Step 2: Configuring ufw to Allow Forwarding
Even though the kernel is now willing to route packets, ufw will immediately block them because its default routing policy is to drop forwarded traffic.
Open the primary ufw configuration file:
sudo nano /etc/default/ufw
Change the default forward policy from DROP to ACCEPT:
DEFAULT_FORWARD_POLICY="ACCEPT"
Step 3: Configuring NAT (Masquerading)
Now you must write the actual NAT rules. Because NAT occurs in a different “table” within the kernel (the nat table, rather than the standard filter table), you cannot use the simple ufw allow command. You must inject raw iptables syntax directly into ufw’s pre-routing file.
Open the before.rules file:
sudo nano /etc/ufw/before.rules
At the absolute top of this file (before the *filter block), add the following routing block:
# NAT table rules
*nat
:POSTROUTING ACCEPT [0:0]
# Forward traffic from the private subnet (10.0.0.0/24) out through the public interface (eth0)
-A POSTROUTING -s 10.0.0.0/24 -o eth0 -j MASQUERADE
# Commit the rules
COMMIT
Note: You must ensure that eth0 is the actual name of your public-facing interface (check using ip addr). If your interface is named enp3s0, you must update the rule accordingly.
With this rule in place, if the internal database server (10.0.0.5) attempts to ping 8.8.8.8, the Ubuntu server will intercept the packet, strip off the internal 10.0.0.5 address, stamp it with its own public IP, send it to the internet, and seamlessly route the reply back to the database.
Step 4: Configuring Port Forwarding
Suppose you want external developers to access the private database server on port 3306. You need to instruct the Ubuntu router to intercept traffic arriving on its public IP at port 3306 and forward it to 10.0.0.5:3306.
Open the same before.rules file. You will add a PREROUTING rule to the *nat block you just created.
# NAT table rules
*nat
:PREROUTING ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
# Port Forwarding: Route public port 3306 to internal 10.0.0.5 port 3306
-A PREROUTING -i eth0 -p tcp --dport 3306 -j DNAT --to-destination 10.0.0.5:3306
# Masquerade outbound traffic
-A POSTROUTING -s 10.0.0.0/24 -o eth0 -j MASQUERADE
# Commit the rules
COMMIT
Step 5: Applying and Testing the Configuration
Because you have modified the underlying configuration files, you must completely restart ufw for it to compile and inject the new iptables rules into the kernel.
sudo ufw disable
sudo ufw enable
To verify that the NAT rules were injected successfully, you can query the raw kernel nat table directly using iptables:
sudo iptables -t nat -L -n -v
You should see your DNAT (Destination NAT for Port Forwarding) rule listed under the PREROUTING chain, and your MASQUERADE rule listed under the POSTROUTING chain.
Conclusion
While ufw is marketed as a simple tool for managing local ports, it acts as a highly capable frontend for the complex Linux netfilter stack. By enabling kernel IP forwarding and injecting raw NAT rules into the pre-routing files, Ubuntu administrators can transform a standard Linux VM into a fully functional, high-performance edge router and firewall for entire private subnets.