3 min read

How to Set up Port Forwarding with iptables

If you have a server on a private network and need to access it from the outside (but can't simply give it an external IP) you can use port forwarding on an externally accessible server to get around it. Once set up it simply sends all incoming packets that meet certain criteria to a new IP. That way you can connect to a public server in order to communicate with the private server.

Setup

All following steps need the to be done on the externally accessible machine and need to be run as with root privilege's. I'll assume a scenario where you want to access an internal server with an internal IP 10.0.0.1 on port 80 through a server with an external IP 32.0.0.1 on port 8080 using TCP.

First we need to allow forwarding on the kernel level as this is usually disabled by default. Open /etc/sysctl.conf with your favorite editor (and root priviliges) and uncomment the line net.ipv4.ip_forward=1. Now run

sudo sysctl -p sudo sysctl --system

to apply the setting.

The forwarding rule itself can be added as follows:

iptables -t nat -A PREROUTING -p tcp -d 32.0.0.1 --dport 8080 -j DNAT --to-destination 10.0.0.1:80

Let's break that down. -t nat tells iptables that we want to work on the Network Address Translation (NAT) table. We add our rule to the PREROUTING chain as we want to re-route packets and select them based on protocol (-p tcp), destination (-d 32.0.0.1) and port (--dport 8080). We specify that we want to apply Destination NAT (DNAT) to the selected packets (-j DNAT) and of course the target IP and port with --to-destination 10.0.0.1:80.

If you only care about the port because the public server has multiple IPs and you want the rule to work for all of them simply omit -d 32.0.0.1.

Now that all incoming traffic will be re-routed we just need to tell iptables to change the source address in the re-routed packages. If we don't the target server will think they came from our local machine directly and try to respond directly which doesn't sit well with TCP. To do that we need to run the following:

iptables -t nat -A POSTROUTING ! -s 127.0.0.1 -j MASQUERADE

Now iptables will rewrite the origin of the re-rerouted packages so the target server will answer to the correct machine. I added ! -s 127.0.0.1 to exclude packets originating in [localhost](http://localhost) as without it the rule broke DNS resolution to point that sudo didn't work properly anymore because it couldn't resolve its own hostname.

Making it permanent

Iptables doesn't persist rules through restarts on its own. There are packages to take care of that like iptables-persistent but that doesn't seem to be available on Ubuntu 18.04 so here's how to do it manually.

The ruleset can be easily saved by running iptables-save > /etc/iptables.rules and restored with iptables-restore < /etc/iptables.rules. Where you save your rules it up to you.

Previous Ubuntu versions used ifupdown for networking which provides simple pre-up and pre-down hooks which are a good place to run the save and restore commands:

# open the interface definitions sudo nano /etc/network/interfaces # find your interface and add the following pre-up iptables-restore < /etc/iptables.rules pre-down iptables-save > /etc/iptables.rules

If your /etc/network/interfaces contains

# ifupdown has been replaced by netplan(5) on this system. See # /etc/netplan for current configuration. # To re-enable ifupdown on this system, you can run: # sudo apt install ifupdown

you'll need to use networkd-dispatcher as netplan doesn't support hooks:

echo 'iptables-restore < /etc/iptables.rules' | sudo tee /etc/networkd-dispatcher/routable.d/50-iptables-restore echo 'iptables-save > /etc/iptables.rules' | sudo tee /etc/networkd-dispatcher/off.d/50-iptables-save

The save on pre-down and in off.d hooks is optional, it's actually safer to leave it out in case you mess something up. That way you can just restart and have the old (working) ruleset running again.

And that's it, know you have a port-forwarding setup that is persistent through reboots and doesn't break DNS resolution!