NAT introduction
,
or network address translation, is a general term for managing packets in order to redirect them to an alternate address. Typically, this is used to allow traffic to transcend network boundaries. A host that implements NAT typically has access to two or more networks and is configured to route traffic between them.
Port forwarding is the process of forwarding requests for a specific port to another host, network, or port. As this process modifies the destination of the packet in flight, it is considered a type of NAT operation.
In this tutorial, we will demonstrate how to use iptables to forward ports to hosts behind a firewall using NAT techniques. This is useful if you have set up a private network, but still want to allow some traffic through a designated gateway machine.
Prerequisites
To follow this guide, you’ll need:
- Two Ubuntu 20.04 servers configured in the same datacenter with private networks enabled. On each of these machines, you will need to set up a non-root user account with sudo privileges. You can learn how to do this with our guide on Ubuntu 20.04 initial server setup guide. Be sure to skip Step 4 of this guide, as we will be installing and configuring the firewall during this tutorial.
- On one of your servers, configure a firewall template with iptables so that it can function as your firewall server. You can do this by following our guide on How to Implement a Basic Firewall with Iptables in Ubuntu 20.04. Once completed, your firewall server should have the following ready to use:
- iptables-persistent installed
- /rules.v4
- An understanding of how to add or adjust rules by editing the rules file or using the iptables command
You saved the default rule set in /etc/iptables
The server in the
that you configure the firewall template will serve as a firewall and router for your private network. For demonstration purposes, the second host will be configured with a web server that can only be accessed using its private interface. It will configure the firewall machine to forward requests received on its public interface to the web server, which it will reach on its private interface.
Before
you
begin, you need to know which interfaces and addresses are being used by both servers
.
To
get the details of your own systems, start by finding your network interfaces. You can find the interfaces on your machines and the addresses associated with them by running the following
:
- ip -4 addr show global scope
Sample output2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast UP state default group QLEN 1000 INET 203.0.113.1/18 BRD 45.55.191.255 Global Reach ET0 valid_lft Forever preferred_lft Forever 3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> MTU 1500 qdisc pfifo_fast UP state default group qlen 1000 inet 10.0.0.1/16 brd 10.132.255.255 global scope eth1 valid_lft forever preferred_lft forever
The highlighted output shows two interfaces (eth0 and eth1) and the addresses assigned to each (203.0.113.1 and 10.0.0.1 respectively). To find out which of these interfaces is your public interface, run this command:
- ip route show | grep
default Outputdefault via 111.111.111.111 dev eth0 The interface information for this
output (eth0 in this example) will be the interface connected to the default gateway. This is almost certainly its public interface.
Find these values on each of your machines and use them to follow the rest of this guide.
Sample data used in this guide
To clarify things, we will use the following empty address assignments and interfaces throughout this tutorial. Replace your own values with those listed below
: Web server network details: Public IP address: 203.0.113.1 Private IP address: 10.0.0.1 Public interface: eth0 Private interface: eth1
Firewall network details
: Public IP address:
- 203.0.113.2 Private IP address: 10.0.0.2 Public
- eth0
- Private
interface:
interface :
- eth1
Web
server configuration Start connecting to your web server host and
log in with your sudo user
.
Installing
Nginx The first step is to install Nginx on your web server host and lock it down from only listening to your private interface. This will ensure that your web server will only be available if you correctly configure port forwarding.
Start by updating the local package cache
: sudo apt update Next, use apt to
download and install the software:
- sudo apt install nginx
Restrict Nginx to private
network After installing
Nginx
, open the default server block configuration file to ensure that you only listen for the private interface. Open the file with your preferred text editor. Here we will use nano:
- sudo nano /etc/nginx/sites-enabled/default
Inside, find the list directive. It should appear twice in a row towards the top of the configuration
: server { listen 80 default_server; listen [::]:80 default_server ipv6only=on; . . }
In the first listener directive, add your web server’s private IP address and colon before 80 to tell Nginx to only listen on the private interface. We’re only demonstrating IPv4 forwarding in this guide, so you can delete the second listener policy, which is configured for IPv6.
Then, modify the listener policies as follows
: server { listen 10.0.0.1:80 default_server; . . }
Save and close the file when you are finished. If you used nano, you can do so by pressing CTRL+X, then Y, and ENTER.
Now test the file for syntax errors: sudo nginx -t Outputnginx: The
syntax
of the configuration file /etc/nginx/nginx.conf is fine nginx: configuration file /etc/nginx/nginx.conf The test is successful
If there are no errors in the output, restart Nginx to enable the new configuration:
- sudo systemctl restart nginx
Verifying network
restriction At this point, it’s helpful to check the level of access you have to your web server.
From your firewall server,
try to access your web server from the private interface with
the following command:
- curl -connect-timeout 5 10.0.0.1
If successful, your output will result in the following message:
Exit<! DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> <style> body { width: 35em; margin: 0 auto; font-family: Tahoma, Verdana, Arial, sans-serif; } </style> </head> <body> <h1>Welcome to nginx!</h1> . . .
If you try to use the public interface, you receive a message stating that you cannot
connect: curl -connect-timeout 5 203.0.113.1 Outputcurl: (7) Error connecting to port 80
- 203.0.113.1
:
Connection rejected These results are expected
.
Configuring the firewall to forward port 80
You will now work on implementing
port forwarding on your firewall machine. Enabling forwarding in
the kernel
The first thing you need to do is enable traffic forwarding at the kernel level. By default, most systems have forwarding turned off.
To enable port forwarding
for this session only, run the following:
- echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward
Output1
To enable port forwarding permanently, you must edit the /etc/sysctl.conf file. You can do this by opening the privileged
file sudo:
- sudo nano /etc/sysctl.conf
Inside the file
, locate and uncomment the line that reads as follows:
net.ipv4.ip_forward=1
Save and close the file when you are finished
.
Then, apply the settings in this file. First
run the following command: sudo sysctl -p Outputnet.ipv4.ip_forward = 1
Then run the same command, but replace the -p flag with
-system: sudo sysctl -system Output. . . * Applying /usr/lib/
- sysctl.d
/50-pid-max.conf …
kernel.pid_max = 4194304 * Applying /etc/sysctl.d/99-cloudimg-ipv6.conf … net.ipv6.conf.all.use_tempaddr = 0 net.ipv6.conf.default.use_tempaddr = 0 * Applying /etc/sysctl.d/99-sysctl.conf … net.ipv4.ip_forward = 1 * Applying /usr/lib/sysctl.d/protect-links.conf … fs.protected_fifos = 1 fs.protected_hardlinks = 1 fs.protected_regular = 2 fs.protected_symlinks = 1 * Applying /etc/sysctl.conf … net.ipv4.ip_forward = 1
Add forwarding rules
to the basic firewall
Next, you’ll configure your firewall so that traffic flowing to your public interface (eth0) on port 80 is forwarded to your private interface (eth1).
The firewall you configured in the prerequisite tutorial has its FORWARD string set to DROP traffic by default. You must add rules that allow you to forward connections to your web server. For security reasons, it will block this quite strongly so that only the connections you want to forward are allowed.
In the FORWARD chain, you will accept new connections destined for port 80 that come from your public interface and travel to your private interface. New connections are identified by the conntrack extension and will be represented specifically by a TCP SYN packet as in the following:
- sudo iptables -A FORWARD -i eth0-o eth1-p tcp -syn -dport 80 -m conntrack -ctstate NEW -j ACCEPT
This will allow the first packet, intended to establish a connection, to pass through the firewall. You will also need to allow any subsequent traffic in both directions that results from that connection. To allow ESTABLISHED and RELATED traffic between the public and private interfaces, run the following commands. First
for your public interface: sudo iptables -A FORWARD -i eth0 -o eth1 -m conntrack -ctstate ESTABLISHED,RELATED -j ACCEPT
Then for your private interface
: sudo iptables -A FORWARD –
- i eth1 -o eth0 -m conntrack -ctstate ESTABLISHED,RELATED -j ACCEPT
Verify that your policy on the FORWARD chain is set to DROP:
- sudo iptables –
- P FORWARD DROP
At this point, you’ve allowed some traffic between your public and private interfaces to proceed through your firewall. However, you haven’t set up the rules that will actually tell iptables how to translate and route traffic.
Add
NAT rules to direct packets successfully
Next, you’ll add rules that tell iptables how to route your traffic. You must perform two separate operations for iptables to successfully modify packets so that clients can communicate with the Web server.
The first operation, called DNAT, will take place in the PREROUTING chain of the nat table. DNAT is an operation that alters the destination address of a packet to allow it to route correctly as it passes between networks. Clients on the public network will connect to the firewall server and have no knowledge of the private network topology. Therefore, you need to modify the destination address of each packet so that when it is sent to your private network, you know how to properly reach your web server.
Since you’re only configuring port forwarding and you’re not performing NAT on every packet that arrives at your firewall, you’ll want to match port 80 on your rule. It will match packets destined for port 80 to the private IP address of your web server (
10.0.0.1 in the example below):
- sudo iptables -t nat -A PREROUTING -i eth0 -p tcp -dport 80 -j DNAT -to-destination 10.0.0.1
This process takes care of half of the image. The packet must be successfully routed to your web server. However, at this time, the package will still have the customer’s original address as the source address. The server will try to send the response directly to that address, making it impossible to establish a legitimate TCP connection.
To configure proper routing, you must also modify the source address of the packet as it exits the firewall en route to the web server. You must modify the source address to the private IP address of the firewall server (10.0.0.2 in the following example). The response will be sent back to the firewall, which can forward it back to the client as expected.
To enable this functionality, add a rule to the POSTROUTING string of the nat table, which is evaluated just before sending packets to the network. It will match packets destined for
your web server by IP address and port:
- sudo iptables -t nat -A POSTROUTING -o eth1 -p tcp -dport 80 -d 10.0.0.1-j SNAT -to-source 10.0.0.2
Once this rule is in place, your web server should be accessible by pointing your web browser to the public address of your firewall machine:
- curl 203.0.113.2
Departure<! DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> <style> body { width: 35em; margin: 0 auto; font-family: Tahoma, Verdana, Arial, sans-serif; } </style> </head> <body> <h1>Welcome to nginx!</h1> . . .
The port forwarding configuration is now complete.
Setting
the permanent rule set
Now that you have configured port forwarding, you can save it
to the permanent rule set.
If you don’t mind losing comments that are in your current rule set, use the netfilter-persistent command to use the iptables service and save your rules:
- sudo service netfilter-persistent save
Output * Saving netfilter rules… run-parts: run /usr/share/netfilter-persistent/plugins.d/15-ip4tables save run-parts: run /usr/share/netfilter-persistent/plugins.d/25-ip6tables save [ OK ]
If you want to keep the comments in your file, open and edit it manually:
- sudo nano /etc/iptables/rules.v4
You will need to adjust the settings in the filter table for the FORWARD string rules that were added. You will also need to adjust the section that configures the nat table so that you can add your PREROUTING and POSTROUTING rules. The content will resemble the following
: *filter # Allow all outgoing packets, but delete incoming and forwarding packets by default :INPUT DROP [0:0] :FORWARD DROP [0:0] :OUTPUT ACCEPT [0:0] # Custom strings per protocol :UDP – [0:0] :TCP – [0:0] :ICMP – [0:0] # Acceptable UDP traffic # Acceptable TCP traffic -A TCP -p tcp -dport 22 -j ACCEPT # Acceptable ICMP traffic # Repetitive acceptance policy -A INPUT -m conntrack -ctstate ESTABLISHED, RELATED -j ACCEPT -A INPUT -i lo -j ACCEPT # Drop invalid packets -A INPUT -m conntrack -ctstate INVALID -j DROP # Pass traffic to specific strings of the protocol ## Only allow new connections (established and related must already be handled) ## For TCP, additionally only allow new SYN packets since that is the only valid method ## to establish a new TCP connection -a input -p udp -m conntrack -ctstate NEW -j UDP -A INPUT -p tcp -syn – m conntrack -ctstate NEW -j TCP -A INPUT -p icmp -m conntrack -ctstate NEW -j ICMP # Reject anything that has fallen to this point ## Try to be protocol-specific with rejection message -A INPUT -p udp -j REJECT -reject-with icmp-port-unreachable -A INPUT -p tcp -j REJECT -reject-with tcp-reset -A INPUT -j REJECT -reject-with icmp-proto-unreachable # Rules for forwarding port 80 to our web server # Web server network details: # * Public IP address: 203.0.113.1 # * Private IP address: 10.0.0.1 # * Public interface: eth0 # * Private interface: eth1 # # Firewall network details: # # * Public IP address: 203.0.113.2 # * Private IP address: 10.0.0.2 # * Public interface: eth0 # * Private interface: eth1 -A FORWARD -i eth0 -o eth1 -p tcp -syn -dport 80 -m conntrack -ctstate NEW -j ACCEPT -A FORWARD -i eth0 -o eth1 -m conntrack -ctstate ESTABLISHED, RELATED -j ACCEPT -FORWARD -i eth1 -o eth0 -m conntrack -ctstate ESTABLISHED,RELATED -j ACCEPT # End of forward filtering rules # Commit changes COMMIT *raw 😛 REROUTING ACCEPT [0:0] :OUTPUT ACCEPT [0:0] COMMIT *nat 😛 REROUTING ACCEPT [0:0] :INPUT ACCEPT [0:0] :OUTPUT ACCEPT [0:0] 😛 OSTROUTING ACCEPT [0:0] # Rules for translating public interface port 80 requests# so that we can successfully forward to the web server using the# Private interface. # Web server network details: # * Public IP address: 203.0.113.1 # * Private IP address: 10.0.0.1 # * Public interface: eth0 # * Private interface: eth1 # # Firewall network details: # # * Public IP address: 203.0.113.2 # * Private IP address: 10.0.0.2 # * Public interface: eth0 # * Private interface: eth1 -A PREROUTING -i eth0 -p tcp -dport 80 -j DNAT -to-destination 10.0.0.1 -A POSTROUTING -d 10.0.0.1 -o eth1 -p tcp -dport 80 -j SNAT -to-source 10.0.0.2# End of the NAT translations for web server COMMIT traffic *security :INPUT ACCEPT [0:0] :FORWARD ACCEPT [0:0] :OUTPUT ACCEPT [0:0] COMMIT *mangle 😛 REROUTING ACCEPT [0:0] :INPUT ACCEPT [0:0] :FORWARD ACCEPT [0:0] :OUTPUT ACCEPT [0:0] 😛 OSTROUTING ACCEPT [0:0] COMMIT
Save and close the file after you have added the contents and adjusted the values to reflect your own network environment.
Next, Test the rules file syntax
: sudo sh -c “iptables-restore –
- t < /etc/iptables/rules.v4″
If no errors are detected, load the rule set:
- sudo service netfilter-persistent reload
Output * Loading netfilter rules… run-parts: run /usr/share/netfilter-persistent/plugins.d/15-ip4tables start run-parts: run /usr/share/netfilter-persistent/plugins.d/25-ip6tables start [ OK ]
Now test that your web server is still accessible through your firewall’s public IP address:
- curl 203.0.113.2
This should work the same as before.
Conclusion
By now, you should be comfortable with port forwarding on a Linux server with iptables. The process involves allowing forwarding at the kernel level, configuring access to allow forwarding of specific port traffic between two interfaces in the firewall system, and configuring NAT rules so that packets can be routed correctly. This may seem like an unwieldy process, but it also demonstrates the flexibility of the netfilter packet filtering framework and iptables firewall. This can be used to disguise your private network topology while allowing service traffic to flow freely through your gateway firewall machine.