Forward Ports Through a Linux Gateway with iptables NAT

Why Your Port Forwarding Isn’t Working

A very common mistake with port forwarding on Linux is assuming a single DNAT rule is enough.
You add one iptables rule, try to hit your server from outside, and… nothing.

In reality, proper forwarding through a Linux gateway needs three things working together:
IP forwarding in the kernel, packet filtering rules in the FORWARD chain, and DNAT/SNAT rules in the nat table.
Miss one of those, and packets just die quietly on the way.

This guide is for you if:

  • You have a Linux box acting as a gateway between two networks.
  • You want to forward a port (for example, HTTP on port 80) from the gateway to an internal web server.
  • You are using iptables on Ubuntu with the usual netfilter-persistent setup.

We’ll walk through the full path: enabling IP forwarding, allowing the traffic in the firewall, and setting up DNAT and SNAT so replies can find their way back.


Know What You’re Building: Simple Gateway Layout

Let’s keep the mental model simple.
You have:

  • One Linux machine acting as a gateway between networks.
  • An external side where the clients come from.
  • An internal side with a private network and your real web server.

Port forwarding means: when someone connects to the gateway’s external IP on a port (say port 80), the gateway quietly sends that traffic to the internal web server instead.
Because the gateway changes packet addresses in transit, this is NAT territory, handled by iptables rules in the nat table.

Two main NAT operations are involved:

  • DNAT (Destination NAT) – change the destination IP so packets go to the internal server.
  • SNAT (Source NAT) – change the source IP on the way back, so reply packets can be routed correctly.

We’ll also use the filter table’s FORWARD chain to allow the forwarded packets through.


Step 1 – Enable IP Forwarding in the Kernel

If the kernel won’t forward packets, no amount of iptables magic will help.
Linux needs IP forwarding turned on to behave like a gateway instead of just a host.

Temporarily enable IP forwarding

This lets you test quickly without touching config files yet.

sudo sysctl -w net.ipv4.ip_forward=1

That flips the kernel flag on for IPv4 forwarding until the next reboot.
You can verify with:

sysctl net.ipv4.ip_forward

You should see:

net.ipv4.ip_forward = 1

Make IP forwarding persistent (recommended)

If you’re happy with the setup and want it to survive reboots, add it to the sysctl configuration.
On Ubuntu, you can put it into a config file under /etc/sysctl.d/.

echo 'net.ipv4.ip_forward=1' | sudo tee /etc/sysctl.d/99-ip-forward.conf
sudo sysctl --system

sysctl --system reloads all sysctl configs, so the new value is applied immediately and at every boot.

Common mistake: forgetting this step and wondering why everything breaks after a reboot.


Step 2 – Allow Forwarded Traffic in the Filter Table

Even with IP forwarding on, the default firewall policy can still block packets.
Forwarded packets go through the FORWARD chain in the filter table.

If that chain drops packets by default (very common on hardened setups), your DNAT/SNAT won’t matter.
So you need to explicitly allow the traffic you want to forward.

You’ll typically add rules like:

  • Allow packets going to the internal server on the desired port.
  • Allow the return traffic so connections can complete.

For example, to allow forwarded HTTP traffic (port 80) between networks, you’d use rules in the FORWARD chain.
The exact command syntax depends on your IP ranges and interface names, but the idea is the same: permit the packets before they hit a drop rule.

If you’re using a persistent firewall setup (via the usual netfilter-persistent service), remember that rules in the filter table need to be saved so they survive reboots.

Safety tip: before you change firewall rules on a remote box, make sure you have console or out-of-band access in case you lock yourself out.


Step 3 – Add DNAT Rules to Send Traffic to the Internal Server

Next is the part most people think of as “port forwarding”: DNAT.

DNAT rules live in the nat table and usually the PREROUTING chain.
That’s where the kernel first sees incoming packets and can rewrite their destination before routing.

The goal is simple: when traffic hits the gateway’s external IP and some port (for example, TCP port 80), rewrite the destination address to your internal web server’s IP.

Conceptually the DNAT rule says:

If a packet arrives on the gateway for port 80, change its destination to the internal web server’s address.

In iptables language, this becomes a -t nat -A PREROUTING rule with a -j DNAT target.

Once this rule is in place, packets that would have been delivered to the gateway are instead routed onward to the internal server.

Common mistake: pointing DNAT to an internal IP but forgetting that the internal network must actually be reachable via one of the gateway’s interfaces.
If routing isn’t correct, packets just disappear.


Step 4 – Add SNAT Rules So Replies Come Back Cleanly

DNAT sends the request in the right direction, but without SNAT, replies can get confused.

Imagine the internal web server receives a packet that appears to come from some external client.
It sends the reply straight to that client’s IP, but the route back doesn’t know anything about going through your gateway.
Or the client drops the packet because it doesn’t match the connection it opened.

That’s why you add SNAT in the POSTROUTING chain of the nat table.

SNAT rewrites the source address of packets leaving your internal network so they look like they’re coming from the gateway itself.
Then, replies from the internal server go back to the gateway, which can undo the NAT translation and forward them back to the original client.

Conceptually, the SNAT rule says:

When packets leave the internal network bound for the external side, change the source address to the gateway’s external IP.

This keeps the whole conversation symmetrical and makes routing simple.

Common mistake: only configuring DNAT.
Everything looks fine on the way in, but nothing comes back, and you see half-open connections or timeouts.


Step 5 – Save iptables Rules Persistently

On Ubuntu, iptables rules don’t stick around after a reboot unless you save and restore them.
The usual setup uses iptables-persistent together with the netfilter-persistent service.

Once your DNAT, SNAT, and FORWARD rules are working, save them.
Otherwise, the next restart will reset the firewall and your port forwarding will vanish.

The restore process is handled by the netfilter-persistent service at boot.
It loads the saved rules so your NAT and filter tables look the same as before.

Safety note: when experimenting, it’s smart to keep a copy of your working iptables configuration before major changes.
That way, if a new rule breaks traffic, you can restore the previous file instead of rebuilding from memory.


Step 6 – Test, Validate, and Avoid Silent Failures

Once the pieces are in place, you want proof that traffic is really flowing.
Don’t just trust that iptables -L shows rules; actually hit the forwarded port from the external side.

A basic test path:

  1. From an external machine, try to connect to the gateway’s external IP on the forwarded port (for example, curl http://gateway-external-ip/).
  2. Confirm the internal web server sees the request and sends a response.
  3. Confirm the external client gets the expected response instead of a timeout.

If something breaks:

  • If you get no response, suspect IP forwarding or the FORWARD chain rules.
  • If the internal server never sees traffic, suspect your DNAT rule.
  • If the internal server sees requests but clients never see replies, suspect SNAT and return path routing.

It’s easy to lose an hour here if you chase the wrong piece.
Keep the mental model: kernel forwarding → filter FORWARD rules → DNAT (PREROUTING) → SNAT (POSTROUTING).
Work through them in that order.


Quick Recap and Next Step

To forward ports through a Linux gateway with iptables, you need more than one magic command.
You:

  1. Enable IP forwarding at the kernel level with sysctl and make it persistent.
  2. Open the way in the FORWARD chain of the filter table so packets aren’t silently dropped.
  3. Use DNAT rules in the nat table’s PREROUTING chain to send traffic to the internal server.
  4. Use SNAT rules in the nat table’s POSTROUTING chain so replies return cleanly to the original client.
  5. Save your iptables rules so the setup survives reboots.

Keep your changes small, test each step, and watch out for the classic “DNAT without SNAT” problem.

If this worked for you, keep CrushEdge handy for the next fix.

No Comments

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.