Sr Technical Writer

NAT, or network address translation, is a general term for mangling packets in order to redirect them to an alternative address. Usually, 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. Two key operations make this possible: DNAT (Destination NAT), which rewrites the destination address of incoming packets, and SNAT (Source NAT), which rewrites the source address of outgoing packets so that replies can route back correctly.
In this tutorial, you will learn how to use iptables to forward ports through a Linux gateway using NAT techniques, including DNAT and SNAT rules in the PREROUTING and POSTROUTING chains. This is useful if you’ve configured a private network but still want to allow certain traffic inside through a designated gateway machine. You will set up IP forwarding at the kernel level, configure the firewall’s packet filtering rules, and write NAT table entries to route port 80 traffic to an internal web server.
Note: This tutorial was originally written for Ubuntu 20.04 and has been validated on Ubuntu 22.04 and Ubuntu 24.04 LTS. The iptables commands, kernel forwarding parameters, and netfilter-persistent service used in this guide work the same way across all current Ubuntu LTS releases. If you are running a different Ubuntu version, the steps will still apply as long as the iptables and iptables-persistent packages are installed.
sysctl, FORWARD chain rules in the filter table, and DNAT/SNAT rules in the nat table.PREROUTING chain) rewrites the destination address of incoming packets so they reach the correct internal server. SNAT (configured in the POSTROUTING chain) rewrites the source address so replies route back through the gateway.conntrack) allows iptables to identify new connections and permit established/related traffic to flow in both directions, keeping the FORWARD chain secure while allowing legitimate return traffic.netfilter-persistent or by editing /etc/iptables/rules.v4 directly to ensure rules persist across reboots.To follow along with this guide, you will need:
sudo privileges. You can learn how to do this with our Ubuntu initial server setup guide. Make sure to skip Step 4 of that guide since you will be setting up and configuring the firewall during this tutorial.iptables so it can function as your firewall server. You can do this by following our guide on How To Implement a Basic Firewall with Iptables on Ubuntu 20.04. Once completed, your firewall server should have the following ready to use:
iptables-persistent installed/etc/iptables/rules.v4iptables commandIf you are new to iptables, reviewing the iptables essentials guide will help you become familiar with common firewall rules and commands before proceeding.
The server on which you set up your firewall template will serve as the firewall and gateway for your private network. For demonstration purposes, the second host will be configured with a web server that is only accessible using its private interface. You’ll be configuring 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 what interfaces and addresses are being used by both of your servers.
To get the details of your own systems, begin 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 scope global
Sample Output2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
inet 203.0.113.1/18 brd 45.55.191.255 scope global eth0
valid_lft forever preferred_lft forever
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
inet 10.0.0.1/16 brd 10.132.255.255 scope global 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 from this output (eth0 in this example) will be the interface connected to your default gateway. This is almost certainly your public interface.
Find these values on each of your machines and use them to follow along with the rest of this guide.
To make things clearer, we’ll be using the following empty address and interface assignments throughout this tutorial. Please substitute your own values for the ones listed in the following:
Web server network details:
203.0.113.110.0.0.1eth0eth1Firewall network details:
203.0.113.210.0.0.2eth0eth1Begin connecting to your web server host and by logging in with your sudo user.
The first step is to install Nginx on your web server host and lock it down so that it only listens to its private interface. This will ensure that your web server will only be available if you correctly set up port forwarding.
Begin by updating the local package cache:
- sudo apt update
Next, use apt to download and install the software:
- sudo apt install nginx
After Nginx is installed, open up the default server block configuration file to ensure that it only listens to the private interface. Open the file using your preferred text editor. Here we’ll use nano:
- sudo nano /etc/nginx/sites-enabled/default
Inside, find the listen directive. It should be listed twice in a row towards the top of the configuration:
server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;
. . .
}
At the first listen directive, add your web server’s private IP address and a colon before the 80 to tell Nginx to only listen on the private interface. We’re only demonstrating IPv4 forwarding in this guide, so you can remove the second listen directive, which is configured for IPv6.
Next, modify the listen directives 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 this by pressing CTRL + X, then Y, and ENTER.
Now test the file for syntax errors:
- sudo nginx -t
Outputnginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
If there are no errors in the output, restart Nginx to enable the new configuration:
- sudo systemctl restart nginx
At this point, it’s useful to verify 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:
Output<!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’ll receive a message that says it cannot connect:
- curl --connect-timeout 5 203.0.113.1
Outputcurl: (7) Failed to connect to 203.0.113.1 port 80: Connection refused
These results are expected.
Now you will work on implementing port forwarding on your firewall machine.
The first thing you need to do is enable traffic forwarding at the kernel level. By default, most systems have forwarding turned off.
To turn port forwarding on for this session only, run the following:
- echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward
Output1
To turn port forwarding on permanently, you will have to edit the /etc/sysctl.conf file. You can do this by opening the file with sudo privileges:
- sudo nano /etc/sysctl.conf
Inside the file, find 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
Next, you will configure your firewall so that traffic flowing into your public interface (eth0) on port 80 will be forwarded to your private interface (eth1).
The firewall you configured in the prerequisite tutorial has your FORWARD chain set to DROP traffic by default. You need to add rules that will allow you to forward connections to your web server. For security’s sake, you’ll lock this down fairly tightly so that only the connections you wish to forward are allowed.
In the FORWARD chain, you’ll accept new connections destined for port 80 that are coming from your public interface and traveling to your private interface. New connections are identified by the conntrack extension and will specifically be represented 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 let the first packet, meant to establish a connection, through the firewall. You’ll also need to allow any subsequent traffic in both directions that results from that connection. To allow ESTABLISHED and RELATED traffic between your 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
Double check that your policy on the FORWARD chain is set to DROP:
- sudo iptables -P FORWARD DROP
At this point, you’ve allowed certain traffic between your public and private interfaces to proceed through your firewall. However, you haven’t configured the rules that will actually tell iptables how to translate and direct the traffic.
Next, you will add the rules that will tell iptables how to route your traffic. You need to perform two separate operations in order for iptables to correctly alter the 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 a packet’s destination address in order to enable it to correctly route as it passes between networks. The clients on the public network will be connecting to your firewall server and will have no knowledge of your private network topology. Therefore, you need to alter the destination address of each packet so that when it is sent out on your private network, it knows how to correctly reach your web server.
Since you’re only configuring port forwarding and not performing NAT on every packet that hits your firewall, you’ll want to match port 80 on your rule. You will match packets aimed at port 80 to your web server’s private IP address (10.0.0.1 in the following example):
- 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 picture. The packet should get routed correctly to your web server. However, right now, the packet will still have the client’s original address as the source address. The server will attempt to send the reply directly to that address, which will make it impossible to establish a legitimate TCP connection.
On DigitalOcean, packets leaving a Droplet with a different source address will actually be dropped by the hypervisor, so your packets at this stage will never even make it to the web server (which will be fixed by implementing SNAT momentarily). This is an anti-spoofing measure put in place to prevent attacks where large amounts of data are requested to be sent to a victim’s computer by faking the source address in the request.
To configure proper routing, you also need to modify the packet’s source address as it leaves the firewall en route to the web server. You need to modify the source address to your firewall server’s private IP address (10.0.0.2 in the following example). The reply will then be sent back to the firewall, which can then forward it back to the client as expected.
To enable this functionality, add a rule to the POSTROUTING chain of the nat table, which is evaluated right before packets are sent out on the network. You’ll match the 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
SNAT vs MASQUERADE: In this guide we use SNAT because we have a static private IP address on the firewall. If your gateway’s IP address is dynamically assigned (for example, via DHCP), use the MASQUERADE target instead. MASQUERADE automatically uses the outgoing interface’s current IP address, but it is slightly slower than SNAT because it must look up the address for every packet. For static IPs, SNAT is the better choice.
Once this rule is in place, your web server should be accessible by pointing your web browser at your firewall machine’s public address:
- curl 203.0.113.2
Output<!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>
. . .
Your port forwarding setup is now complete.
Now that you have set up port forwarding, you can save this to your permanent rule set.
If you do not care about losing the 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: executing /usr/share/netfilter-persistent/plugins.d/15-ip4tables save
run-parts: executing /usr/share/netfilter-persistent/plugins.d/25-ip6tables save
[ OK ]
If you would like to keep the comments in your file, open it up and edit manually:
- sudo nano /etc/iptables/rules.v4
You will need to adjust the configuration in the filter table for the FORWARD chain rules that were added. You will also need to adjust the section which configures the nat table so that you can add your PREROUTING and POSTROUTING rules. The contents will resemble the following:
*filter
# Allow all outgoing, but drop incoming and forwarding packets by default
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
# Custom per-protocol chains
: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
# Boilerplate 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 protocol-specific chains
## Only allow new connections (established and related should already be handled)
## For TCP, additionally only allow new SYN packets since that is the only valid
## method for establishing 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's fallen through to this point
## Try to be protocol-specific w/ 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 to forward 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
-A FORWARD -i eth1 -o eth0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# End of Forward filtering rules
# Commit the changes
COMMIT
*raw
:PREROUTING ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
COMMIT
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
# Rules to translate requests for port 80 of the public interface
# so that we can forward correctly 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 NAT translations for web server traffic
COMMIT
*security
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
COMMIT
*mangle
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
COMMIT
Save and close the file once you’ve added the content and adjusted the values to reflect your own network environment.
Next, test the syntax of your rules file:
- 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: executing /usr/share/netfilter-persistent/plugins.d/15-ip4tables start
run-parts: executing /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 it did before.
When configuring iptables port forwarding on a Linux gateway, you should follow these security best practices to keep your network protected:
Restrict forwarded ports to specific services. Only forward the ports that are strictly necessary. Each forwarded port creates an entry point into your private network, so avoid opening ports you don’t need.
Limit source IP addresses when possible. If you know which external IP addresses need access, add a source filter to your PREROUTING rule. For example, to allow only 198.51.100.0/24 to reach your web server through port 80:
- sudo iptables -t nat -A PREROUTING -i eth0 -s 198.51.100.0/24 -p tcp --dport 80 -j DNAT --to-destination 10.0.0.1
- sudo iptables -A FORWARD -j LOG --log-prefix "IPTables-FORWARD-DROP: " --log-level 4
limit module to your FORWARD rule:- sudo iptables -A FORWARD -i eth0 -o eth1 -p tcp --syn --dport 80 -m conntrack --ctstate NEW -m limit --limit 25/second --limit-burst 50 -j ACCEPT
Regularly review your rules. Use iptables -L -v -n and iptables -t nat -L -v -n to list and audit your firewall rules periodically. Stale or overly permissive rules can leave your network exposed.
Keep iptables up to date. Run sudo apt update && sudo apt upgrade regularly to apply security patches.
For a broader look at firewall strategies, read How To Choose an Effective Firewall Policy to Secure Your Servers.
If your port forwarding is not working as expected, work through the following checklist to identify the problem.
1. Verify IP forwarding is enabled:
- cat /proc/sys/net/ipv4/ip_forward
This should return 1. If it returns 0, IP forwarding is disabled. Re-enable it using the steps in the “Enabling Forwarding in the Kernel” section above.
2. Check your NAT rules are loaded correctly:
- sudo iptables -t nat -L -v -n
Look for your PREROUTING and POSTROUTING rules. Make sure the destination IP, port, and interface names are correct.
3. Inspect the FORWARD chain:
- sudo iptables -L FORWARD -v -n
Confirm that your FORWARD rules appear before the default DROP policy. Rules in iptables are evaluated top to bottom, so ordering matters.
4. Verify the backend service is running:
- curl --connect-timeout 5 10.0.0.1
If this times out from the firewall machine, the problem is with the web server, not the firewall rules. Check that Nginx is running and listening on the correct private IP address.
5. Check for cloud firewall conflicts:
If you are running on DigitalOcean or another cloud provider, external cloud firewalls may be blocking traffic before it reaches your Droplet’s iptables rules. Make sure your DigitalOcean Cloud Firewall (if configured) allows inbound TCP traffic on port 80 to your firewall server’s public IP.
6. Watch traffic in real time with tcpdump:
To see whether packets are arriving at your firewall’s public interface:
- sudo tcpdump -i eth0 -n tcp port 80
If you see incoming SYN packets but no forwarded traffic on eth1, the issue is likely in your FORWARD or NAT rules.
| Symptom | Likely Cause | Fix |
|---|---|---|
| Connection refused | Backend not listening on correct IP | Verify Nginx listen directive |
| Connection timeout | FORWARD chain dropping packets | Check FORWARD rules and ordering |
| Asymmetric routing | Missing SNAT rule | Add POSTROUTING SNAT rule |
| No packets on public interface | Cloud firewall blocking | Update cloud firewall inbound rules |
| Rules lost after reboot | Rules not saved | Run netfilter-persistent save |
Starting with Debian 10 (Buster) and Ubuntu 20.10, the Linux ecosystem has been transitioning from iptables to nftables as the default packet filtering framework. On Ubuntu 20.04, 22.04, and 24.04 LTS, the iptables command still works because Ubuntu provides iptables-nft, a compatibility layer that translates iptables syntax into the nftables backend. This means the commands in this tutorial will work on all current Ubuntu LTS releases without modification.
Here is a quick comparison:
| Feature | iptables | nftables |
|---|---|---|
| Syntax | Separate commands per table (iptables, ip6tables, arptables, ebtables) |
Single unified tool (nft) |
| Performance | Traverses chains linearly | Uses optimized lookups with sets and maps |
| Atomicity | Rules applied one at a time | Entire ruleset applied atomically |
| IPv4/IPv6 | Separate tools for each | Handles both in a single rule |
| Backward Compatibility | Native | Provides iptables-nft compatibility layer |
If you want to write the same port forwarding rules using native nftables syntax, it would look like this:
- sudo nft add table ip nat
- sudo nft add chain ip nat prerouting { type nat hook prerouting priority -100 \; }
- sudo nft add chain ip nat postrouting { type nat hook postrouting priority 100 \; }
- sudo nft add rule ip nat prerouting iif eth0 tcp dport 80 dnat to 10.0.0.1
- sudo nft add rule ip nat postrouting oif eth1 ip daddr 10.0.0.1 tcp dport 80 snat to 10.0.0.2
For most Ubuntu LTS users, the iptables commands shown in this guide remain fully supported through the compatibility layer. If you are setting up new infrastructure or planning a migration, learning native nftables syntax is worth the investment.
For more on managing firewall rules, see UFW Essentials: Common Firewall Rules and Commands, which covers the UFW frontend that sits on top of iptables/nftables.
When running your Linux gateway on a cloud provider such as DigitalOcean, there are additional networking considerations to keep in mind.
VPC and private networking: On DigitalOcean, private networking between Droplets is provided through VPC (Virtual Private Cloud). Your Droplets must be in the same VPC to communicate over private IP addresses. When setting up port forwarding, make sure both your firewall and web server Droplets are in the same VPC and region.
Cloud firewalls and iptables: DigitalOcean Cloud Firewalls are applied at the hypervisor level, before traffic reaches your Droplet. This means cloud firewall rules are evaluated before your iptables rules. If you are using both, make sure:
Anti-spoofing: As mentioned earlier, DigitalOcean’s hypervisor drops packets with source addresses that don’t match the Droplet’s assigned IP. This is why the SNAT rule in the POSTROUTING chain is essential. Without it, your web server’s responses (with the client’s original source address) would be dropped by the hypervisor before leaving the network.
Floating IPs: If your firewall Droplet uses a DigitalOcean Reserved IP (formerly Floating IP), the public traffic arrives on a special anchor interface. You may need to adjust your PREROUTING rule’s -i interface accordingly. Check your interfaces with ip addr to identify the correct one.
DNAT (Destination NAT) changes the destination IP address of an incoming packet. It is used in the PREROUTING chain so packets arrive at the correct internal server. SNAT (Source NAT) changes the source IP address of an outgoing packet, and it is used in the POSTROUTING chain so that reply packets route back through the gateway instead of being sent directly to the client. In a port forwarding setup, you typically need both: DNAT to redirect the incoming request and SNAT to ensure the response returns through the firewall. For a deeper understanding of how packets traverse the iptables chains, see A Deep Dive into Iptables and Netfilter Architecture.
IP forwarding is the kernel-level feature that allows a Linux machine to pass packets between its network interfaces. Without it enabled (net.ipv4.ip_forward=1 in /etc/sysctl.conf), the kernel will drop any packet that arrives on one interface and is destined for another. Since port forwarding means receiving a packet on the public interface and sending it out through the private interface, IP forwarding must be turned on for the process to work.
You can forward multiple ports by adding separate DNAT, SNAT, and FORWARD rules for each port. Alternatively, use the multiport extension to match several ports in a single rule. For example, to forward both ports 80 and 443:
- sudo iptables -t nat -A PREROUTING -i eth0 -p tcp -m multiport --dports 80,443 -j DNAT --to-destination 10.0.0.1
- sudo iptables -t nat -A POSTROUTING -o eth1 -p tcp -m multiport --dports 80,443 -d 10.0.0.1 -j SNAT --to-source 10.0.0.2
- sudo iptables -A FORWARD -i eth0 -o eth1 -p tcp -m multiport --dports 80,443 --syn -m conntrack --ctstate NEW -j ACCEPT
You can also specify port ranges using the format 8000:9000. For more examples of common iptables rules, see Iptables Essentials: Common Firewall Rules and Commands.
Yes. The rules are similar, but you replace -p tcp with -p udp and remove the --syn flag (since UDP is connectionless). For example, to forward UDP port 53 (DNS) to an internal server:
- sudo iptables -t nat -A PREROUTING -i eth0 -p udp --dport 53 -j DNAT --to-destination 10.0.0.1
- sudo iptables -t nat -A POSTROUTING -o eth1 -p udp --dport 53 -d 10.0.0.1 -j SNAT --to-source 10.0.0.2
- sudo iptables -A FORWARD -i eth0 -o eth1 -p udp --dport 53 -m conntrack --ctstate NEW -j ACCEPT
The conntrack module tracks UDP “connections” based on the source/destination address and port pair, so established and related rules still apply for return traffic.
While nftables is the newer framework and has been the default backend since Debian 10, iptables is not fully deprecated. Ubuntu LTS releases (20.04, 22.04, 24.04) ship with iptables-nft, which translates iptables commands into the nftables kernel API. This means you can continue using the familiar iptables syntax without issues. However, for new projects, learning nftables is recommended because it offers better performance, a simpler unified syntax, and atomic rule updates.
By now, you should be comfortable with forwarding ports on a Linux gateway with iptables. The process involves three main steps: enabling IP forwarding at the kernel level, configuring FORWARD chain rules to allow traffic between interfaces, and setting up DNAT and SNAT rules in the NAT table so that packets are routed and translated correctly.
You also learned security best practices for port forwarding, how to troubleshoot common issues, and how iptables compares to the newer nftables framework. This approach works across Ubuntu 20.04, 22.04, and 24.04 LTS, giving you a reliable method for controlling how traffic flows through your gateway firewall machine.
For related learning, explore these resources:
Want to simplify your networking? DigitalOcean Cloud Firewalls let you define inbound and outbound rules at the infrastructure level without managing iptables on each server. Combined with VPC for private networking and Droplets for scalable compute, you can build secure, private networks with minimal configuration. Get started with a free DigitalOcean account and try it today.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
I help Businesses scale with AI x SEO x (authentic) Content that revives traffic and keeps leads flowing | 3,000,000+ Average monthly readers on Medium | Sr Technical Writer @ DigitalOcean | Ex-Cloud Consultant @ AMEX | Ex-Site Reliability Engineer(DevOps)@Nutanix
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
Excuse me,but I have a question.Is is a proxy server.If not,how are they different?
so, this is a lot of info. I would like to offer a shorter bit of info that might help people. In order to forward incoming http connections from port 80 (default) to port 5000 (which was the port my react app was serving on) I did the following:
sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 5000 sudo /sbin/iptables-save
Nice blog. Thanks for putting this up.
I was facing traffic issues in AWS environment having one private subnet, and another subnet connected through VPN to my company network. The packets were not being sent in either direction even after setting ip_forward=1 and all the iptables rules mentioned in this blog.
One thing I realized I had to do was to set the private IP of NAT server as default router in my web server. Just thought of mentioning this - in case someone faces similar problems in future.
Dear Justin,
You change the client ip address to the internal ip address of the firewall which is 192.0.2.15 with the following command:
sudo iptables -t nat -A POSTROUTING -o eth1 -p tcp --dport 80 -d 192.0.2.2 -j SNAT --to-source 192.0.2.15
My question, how can the packet be returned back to the client, since the destination ip address only reach the internal ip address of the firewall? I think the above command does not necessary, as long as the web server has a proper gateway. thanks.
If I have two virtual external NIC in my firewal with two IP @, I want to forward each one to a server on port 80 How I can do this
Thanks for the guide, it did work for me. However, my gateway is also doing IP masquerading/NAT and after applying these rules, I lose all egress from the web server.
After the forwarding port, How can I get network usage on the forwarded port(in this tutorial 80)? Is it possible with iptables? I try it : sudo iptables -A FORWARD -p tcp --dport 80 -j DROP, sudo iptables -A FORWARD -p tcp --dport 80 -m quota --quota 100000 -j ACCEPT, but it doesnt work.
Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.
Full documentation for every DigitalOcean product.
The Wave has everything you need to know about building a business, from raising funding to marketing your product.
Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.
New accounts only. By submitting your email you agree to our Privacy Policy
Scale up as you grow — whether you're running one virtual machine or ten thousand.
Sign up and get $200 in credit for your first 60 days with DigitalOcean.*
*This promotional offer applies to new accounts only.