iptables is actually a front-end for a more complex kernel module called netfilter. When you’re forwarding external ports to internal services, you’re not directly telling your router "send traffic on port 80 to IP 192.168.1.100 port 8080." Instead, you’re instructing netfilter to intercept packets arriving at the router’s external interface, modify their destination address and port, and then pass them along to the internal host.
Let’s say you have a web server running on an internal machine at 192.168.1.100 on port 8080, and you want to make it accessible from the internet on your router’s public IP address, port 80.
Here’s how you’d set that up using iptables:
# Enable IP forwarding
sysctl net.ipv4.ip_forward=1
echo "net.ipv4.ip_forward=1" | tee -a /etc/sysctl.conf
# Create the NAT rule
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j DNAT --to-destination 192.168.1.100:8080
# Allow established and related connections
iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# Allow new connections to the internal server
iptables -A FORWARD -i eth0 -o eth1 -p tcp --dport 8080 -d 192.168.1.100 -j ACCEPT
In this example:
eth0is your router’s external interface (facing the internet).eth1is your router’s internal interface (facing your local network).
The first iptables command, -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j DNAT --to-destination 192.168.1.100:8080, is the core of the port forwarding.
-t nat: We’re working with the NAT table.-A PREROUTING: We’re appending this rule to thePREROUTINGchain. This chain processes packets as soon as they arrive, before any routing decisions are made. This is crucial for Destination NAT (DNAT).-i eth0: The rule applies to packets arriving on theeth0interface.-p tcp: We’re only interested in TCP traffic.--dport 80: The destination port of the incoming packet is 80.-j DNAT: The target action is Destination NAT – change the destination.--to-destination 192.168.1.100:8080: The new destination IP address and port.
The next two rules are for the FORWARD chain, which handles packets that are being routed through the router.
iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT: This allows return traffic for connections that have already been established. Without this, the internal server wouldn’t be able to send responses back to the internet.iptables -A FORWARD -i eth0 -o eth1 -p tcp --dport 8080 -d 192.168.1.100 -j ACCEPT: This explicitly allows new incoming TCP connections destined for port 8080 on192.168.1.100from the external interface (eth0) to be forwarded to the internal interface (eth1). While theDNATrule changes the destination before routing, theFORWARDchain still needs to permit the packet to traverse the router.
When a packet arrives on eth0 for port 80, netfilter hits the PREROUTING rule. It sees the destination is eth0:80 and rewrites the destination to 192.168.1.100:8080. Then, the packet goes through the routing process. Since the destination is now an internal IP, the router will forward it to eth1. The FORWARD chain rules then check if this packet is allowed to pass through the router.
Crucially, the internal server (192.168.1.100) needs to be configured to accept connections on port 8080. When it sends a response back, the source IP address will be 192.168.1.100 and the source port will be 8080. However, the netfilter connection tracking mechanism remembers that this traffic originated from an external connection to your router’s public IP on port 80. When the response packet hits the router on eth1, netfilter sees it’s part of an established connection and performs a Source NAT (SNAT) automatically, changing the source IP back to your router’s public IP and the source port back to 80 before sending it out on eth0. This is often called Masquerading.
This automatic SNAT for return traffic is handled by the MASQUERADE target, or implicitly when using DNAT and allowing forwarding. If you explicitly wanted to see it, you could add a rule like iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE. However, for simple DNAT setups, the kernel usually handles the return traffic SNAT implicitly as part of connection tracking.
The most surprising thing about iptables NAT is that the PREROUTING chain operates on packets before the kernel decides where to send them, and the POSTROUTING chain operates after that decision is made, right before they leave the interface. This allows you to intercept and modify destination addresses (DNAT) before routing, and source addresses (SNAT/MASQUERADE) after routing.
To see the NAT rules in action, you can use iptables -t nat -L -v -n. The -v flag shows packet and byte counts, which will increase as traffic hits your rule, and -n shows numeric output, preventing DNS lookups and making it faster.
The internal web server needs to be configured to listen on 192.168.1.100:8080. If it’s only listening on 127.0.0.1:8080, it won’t receive the forwarded traffic. You can check this with netstat -tulnp | grep 8080 on the internal server.
If you’re using a firewall on the internal server itself, you’ll need to ensure it allows incoming connections on port 8080 from the router’s internal IP address (192.168.1.0/24 or specifically 192.168.1.1).
You’ll also need to make sure your router’s firewall (iptables -A INPUT or iptables -A FORWARD rules on the router itself) permits the traffic to reach the internal server. The iptables -A FORWARD rule shown above is for the router’s perspective, allowing traffic to pass through it.
The next hurdle you’ll likely face is handling multiple internal services on the same external port, or dealing with UDP traffic.