LFCE 82 🐧 Destination NAT and Port Forwarding
Destination NAT is the mechanism that publishes an internal service on a public address. A client connects to the router’s public IP and port, and the router rewrites the destination to the internal server’s private address and port before forwarding the packet. The previous chapter covered source NAT, which rewrites the source of outgoing packets; this chapter covers the opposite direction, which rewrites the destination of incoming packets. The LFCE exam expects the candidate to configure port forwarding, and the nftables dnat statement is the tool that implements it .
The dnat statement belongs in the prerouting chain of the nat table, which is evaluated before the routing decision. A dnat rule in the postrouting chain or the filter table is too late, because the kernel has already decided where to send the packet . The rule matches the incoming interface, the destination port, and optionally the protocol, and it rewrites the destination to the internal server’s address and port. The connection-tracking subsystem records the translation so that the reply can be reversed to the public address.
Key point: The dnat statement requires connection tracking, and it establishes the mapping on the first packet of each connection. Subsequent packets in the same connection use the established mapping without re-evaluating the rule . The prerouting chain must exist even if it has no rules of its own, because the nftables framework requires it to match the incoming packet’s replies . The router must have ip_forward enabled and the forward chain must allow the traffic, or the packet is dropped after the translation.
Why destination NAT requires the prerouting chain
The routing-timing problem.
The routing decision happens after the prerouting chain and before the input and forward chains. A dnat rule in prerouting changes the destination address before the kernel decides whether the packet is for the local host or for forwarding. The kernel then routes the packet to the internal server based on the new destination . A dnat rule in forward is too late, because the routing decision has already been made, and the packet is already on the path to the router’s own stack.
The connection-tracking problem.
The dnat statement requires connection tracking, which is enabled by default in modern kernels. The translation is recorded in the conntrack table, and the reply packet—which is addressed to the client but has the internal server’s source address—is reversed to the public address before it is sent back to the client. Without connection tracking, the router would not know how to reverse the translation, and the reply would have the wrong source address .
The forwarding problem.
Destination NAT does not forward packets by itself. The kernel must have net.ipv4.ip_forward=1, and the forward chain of the filter table must allow the traffic. A dnat rule that is not accompanied by a forwarding rule translates the address but drops the packet, which is the most common misconfiguration . The Red Hat documentation lists the forwarding enablement as an explicit step in the DNAT procedure.
a. The basic port-forwarding rule
The simplest dnat rule publishes an internal service on the router’s public address. The rule matches the incoming interface and the destination port, and it rewrites the destination to the internal server’s address and port.
# Create the nat table and the prerouting chain
sudo nft add table ip nat
sudo nft add chain ip nat prerouting { type nat hook prerouting priority -100\; }
# Forward port 80 to an internal web server
sudo nft add rule ip nat prerouting iifname "eth0" tcp dport 80 dnat to 192.168.1.10:80
The iifname "eth0" expression matches packets that arrive on the public interface, and the tcp dport 80 expression matches the destination port. The dnat to 192.168.1.10:80 statement rewrites the destination to the internal server’s address and port . The priority -100 is the standard destination-NAT priority, which places the chain before the routing decision.
The rule can forward a range of ports or map a public port to a different internal port.
# Forward port 8080 on the public address to port 80 on the internal server
sudo nft add rule ip nat prerouting iifname "eth0" tcp dport 8080 dnat to 192.168.1.10:80
# Forward a range of ports
sudo nft add rule ip nat prerouting iifname "eth0" tcp dport 8000-8010 dnat to 192.168.1.10:8000-8010
The port mapping is the common case for running a service on a non-standard port and publishing it on the standard one . The range form is useful when the internal service uses a block of ports, such as an FTP server or a game server.
b. The forwarding requirement and the SNAT companion
The dnat rule rewrites the destination, but the packet must also be allowed to pass through the forward chain. The filter table’s forward chain must accept the traffic in both directions.
# Allow forwarding from the public interface to the internal interface
sudo nft add rule inet filter forward iifname "eth0" oifname "eth1" ct state new,established,related accept
# Allow the replies from the internal interface to the public interface
sudo nft add rule inet filter forward iifname "eth1" oifname "eth0" ct state established,related accept
The first rule allows the incoming connection to be forwarded to the internal server. The second rule allows the reply to be forwarded back to the client . The ct state match restricts the rules to the connection states that are relevant, which is more precise than allowing all traffic between the interfaces.
The dnat rule is often accompanied by a masquerade or snat rule in the postrouting chain. The Red Hat documentation notes that if the public interface uses a dynamic address, a masquerade rule is needed; if it uses a static address, a SNAT rule is needed. The reason is that the internal server’s reply is addressed to the client, but its source is the internal address, and the client expects the reply from the public address. The SNAT rule rewrites the reply’s source to the public address before it is sent to the client .
# Masquerade the reply from the internal server
sudo nft add rule ip nat postrouting oifname "eth0" masquerade
The masquerade rule rewrites the source of the reply to the eth0 address, which is the address the client connected to. Without it, the client would receive a reply from an address it did not connect to, and the connection would fail . The same rule that was covered in the source-NAT chapter serves the destination-NAT case as well.
c. The redirect statement and local port forwarding
The redirect statement is a special case of destination NAT that redirects packets to the local machine. It is used when the service is on the router itself, not on an internal server. The redirect statement does not take an address, only a port, because the destination is implicitly the local machine.
# Redirect incoming port 80 to a local service on port 8080
sudo nft add rule ip nat prerouting iifname "eth0" tcp dport 80 redirect to 8080
The redirect to 8080 statement rewrites the destination port to 8080 and leaves the address as the router’s own address . The local service on port 8080 receives the packet, and the reply is sent back to the client. The redirect statement is the correct choice when the service is on the router itself, because it avoids the extra hop and the SNAT companion rule.
The redirect statement can also be used in the output chain to redirect locally generated traffic. The LabEx tutorial uses this pattern to redirect local traffic from one loopback port to another.
# Redirect local output traffic from port 18082 to 18083
sudo nft add chain inet lfcs_nat output { type nat hook output priority -100\; }
sudo nft add rule inet lfcs_nat output ip daddr 127.0.0.1 tcp dport 18082 redirect to :18083
The output hook sees packets generated by local processes, and the redirect statement rewrites the destination port. The rule does not start a service on the original port; it rewrites the packet path so that the existing service on the new port receives the traffic . This is useful for testing and for local port remapping.
Complete Example Session
# ============================================
# PART 1: ENABLE IP FORWARDING
# ============================================
sudo sysctl -w net.ipv4.ip_forward=1
echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.d/99-forwarding.conf
# ============================================
# PART 2: CREATE THE NAT TABLE AND PREROUTING CHAIN
# ============================================
sudo nft add table ip nat
sudo nft add chain ip nat prerouting { type nat hook prerouting priority -100\; }
# ============================================
# PART 3: ADD THE DNAT RULE
# ============================================
sudo nft add rule ip nat prerouting iifname "eth0" tcp dport 80 dnat to 192.168.1.10:80
# ============================================
# PART 4: ADD THE FORWARDING RULES
# ============================================
sudo nft add rule inet filter forward iifname "eth0" oifname "eth1" ct state new,established,related accept
sudo nft add rule inet filter forward iifname "eth1" oifname "eth0" ct state established,related accept
# ============================================
# PART 5: ADD THE SNAT COMPANION
# ============================================
sudo nft add rule ip nat postrouting oifname "eth0" masquerade
# ============================================
# PART 6: VERIFY THE NAT RULES
# ============================================
sudo nft list table ip nat
# Output shows the prerouting dnat rule and the postrouting masquerade rule
# ============================================
# PART 7: VERIFY THE FORWARDING RULES
# ============================================
sudo nft list chain inet filter forward
# Output shows the two forwarding rules
# ============================================
# PART 8: TEST FROM AN EXTERNAL CLIENT
# ============================================
# From an external host: curl http://<public-ip>
# The request reaches the internal server at 192.168.1.10
# ============================================
# PART 9: VERIFY THE CONNECTION TRACKING
# ============================================
sudo conntrack -L | grep 192.168.1.10
# Output shows the original destination (public IP) and the translated destination (internal IP)
# ============================================
# PART 10: CLEAN UP
# ============================================
sudo nft flush table ip nat
sudo nft delete table ip nat
The ten parts demonstrate the complete destination-NAT configuration: forwarding, the nat table and prerouting chain, the dnat rule, the forwarding rules, the SNAT companion, the verification, the external test, the connection-tracking check, and the cleanup. The critical steps are Part 3 (the dnat rule) and Part 5 (the masquerade companion), because the dnat rule alone does not forward the packet or rewrite the reply.
Quick Reference
dnat Statement
| Element | Example | Purpose |
|---|---|---|
| Chain | prerouting | Before the routing decision |
| Match | iifname "eth0" tcp dport 80 | Incoming interface and port |
| Action | dnat to 192.168.1.10:80 | Rewrite destination |
| Action (range) | dnat to 192.168.1.10:8000-8010 | Port range |
Companion Rules
| Rule | Chain | Purpose |
|---|---|---|
dnat | nat prerouting | Rewrite destination |
masquerade / snat | nat postrouting | Rewrite reply source |
| Forward accept | filter forward | Allow the traffic |
redirect Statement
| Element | Example | Purpose |
|---|---|---|
| Chain | prerouting or output | Incoming or local traffic |
| Action | redirect to 8080 | Rewrite port to local service |
Verification Commands
| Command | Purpose |
|---|---|
nft list table ip nat | Show the NAT rules |
nft list chain inet filter forward | Show the forwarding rules |
conntrack -L | Show active translations |
sysctl net.ipv4.ip_forward | Confirm forwarding is enabled |
Best Practices
✅ Do This:
# Use prerouting for dnat
nft add rule ip nat prerouting iifname "eth0" tcp dport 80 dnat to 192.168.1.10:80 # ✅
# Add the forwarding rules
nft add rule inet filter forward iifname "eth0" oifname "eth1" ct state new accept # ✅
# Add the SNAT companion for the reply
nft add rule ip nat postrouting oifname "eth0" masquerade # ✅
# Enable IP forwarding
sysctl -w net.ipv4.ip_forward=1 # ✅
# Use redirect for local services
nft add rule ip nat prerouting tcp dport 80 redirect to 8080 # ✅
❌ Don’t Do This:
# Put dnat in postrouting
dnat to 192.168.1.10 # Too late; routing decision made # ❌
# Forget the forwarding rules
# The packet is dropped after the dnat # ❌
# Forget the SNAT companion
# The client receives a reply from the wrong source # ❌
# Use redirect for a remote server
redirect to 8080 # Only for local services # ❌
# Forget ip_forward
# The dnat has no effect # ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| DNAT has no effect | ip_forward disabled | sysctl -w net.ipv4.ip_forward=1 |
| Packet dropped after DNAT | forward chain denies | Allow the traffic in filter forward |
| Reply has wrong source | No SNAT companion | Add masquerade in postrouting |
| Rule not matching | Wrong interface or port | Verify iifname and dport |
| DNAT too late | Rule in postrouting | Move to prerouting |
| Local service not reached | Used dnat instead of redirect | Use redirect for local services |
Real-World Examples
1. Basic DNAT
nft add rule ip nat prerouting iifname "eth0" tcp dport 80 dnat to 192.168.1.10:80
2. Port mapping
nft add rule ip nat prerouting iifname "eth0" tcp dport 8080 dnat to 192.168.1.10:80
3. Port range
nft add rule ip nat prerouting iifname "eth0" tcp dport 8000-8010 dnat to 192.168.1.10:8000-8010
4. Forwarding rule
nft add rule inet filter forward iifname "eth0" oifname "eth1" ct state new accept
5. SNAT companion
nft add rule ip nat postrouting oifname "eth0" masquerade
6. Redirect to local service
nft add rule ip nat prerouting tcp dport 80 redirect to 8080
7. Redirect local output
nft add rule inet lfcs_nat output ip daddr 127.0.0.1 tcp dport 18082 redirect to :18083
8. Enable forwarding
sysctl -w net.ipv4.ip_forward=1
9. Verify NAT rules
nft list table ip nat
10. Verify translations
conntrack -L | grep 192.168.1.10
Visual
Destination NAT Flow
┌──────────────────────────────────────────────────────────────┐
│ DNAT REWRITES THE DESTINATION OF INCOMING PACKETS │
│ │
│ External client │
│ │ dst: 203.0.113.5:80 │
│ ▼ │
│ Router │
│ │ prerouting: dnat to 192.168.1.10:80 │
│ │ → destination rewritten │
│ ▼ │
│ Internal server (192.168.1.10) │
│ │ receives the request │
│ │ reply to the client │
│ ▼ │
│ Router (conntrack reverses) │
│ │ src: 203.0.113.5:80 │
│ ▼ │
│ External client receives the reply │
└──────────────────────────────────────────────────────────────┘
The Three Rules for Port Forwarding
┌──────────────────────────────────────────────────────────────┐
│ DNAT + FORWARD + SNAT │
│ │
│ 1. nat prerouting: dnat to 192.168.1.10:80 │
│ → rewrites the destination │
│ │
│ 2. filter forward: accept │
│ → allows the packet to pass │
│ │
│ 3. nat postrouting: masquerade │
│ → rewrites the reply's source │
│ │
│ All three are required for the connection to succeed. │
└──────────────────────────────────────────────────────────────┘
DNAT vs Redirect
┌──────────────────────────────────────────────────────────────┐
│ DNAT FOR REMOTE; REDIRECT FOR LOCAL │
│ │
│ DNAT: │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ dnat to 192.168.1.10:80 │ │
│ │ → the destination is a different host │ │
│ │ → requires forwarding and SNAT companion │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ Redirect: │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ redirect to 8080 │ │
│ │ → the destination is the local machine │ │
│ │ → no forwarding or SNAT companion needed │ │
│ └────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Statement | dnat to <addr>:<port> |
| Chain | nat prerouting |
| Priority | -100 (standard) |
| Local service | redirect to <port> |
| Prerequisite | net.ipv4.ip_forward=1 |
| Forwarding | filter forward chain |
| Reply source | SNAT/masquerade in postrouting |
| Connection tracking | Required |
| Verification | nft list table ip nat, conntrack -L |
Key takeaways:
- Destination NAT rewrites the destination of incoming packets. The
dnatstatement belongs in thepreroutingchain of thenattable, which is evaluated before the routing decision. Adnatrule inpostroutingorfilteris too late, because the kernel has already decided where to send the packet . - The
dnatrule alone is not enough. The router must haveip_forwardenabled, thefilter forwardchain must allow the traffic, and amasqueradeorsnatrule inpostroutingmust rewrite the reply’s source to the public address. The Red Hat documentation lists all three as explicit steps . - The
preroutingchain must exist even if it has no rules of its own. The nftables framework requires it to match the incoming packet’s replies. The chain is created with thenattype and thepreroutinghook . redirectis the special case for local services. It rewrites the destination port to a service on the router itself, and it does not require the forwarding or SNAT rules because the packet never leaves the router. Thednatstatement is for services on other hosts .- The
dnatstatement supports port ranges and flags. Thednat to 192.168.1.10:8000-8010form maps a range of ports, and thepersistent,random, andfully-randomflags control the port allocation for the connection . - Connection tracking is required. The
dnatstatement establishes the mapping on the first packet of each connection, and the subsequent packets use the established mapping. Theconntrack -Lcommand shows the active translations and confirms that the DNAT is working.
Remember: Destination NAT is the mechanism that publishes an internal service on a public address. The dnat statement rewrites the destination in the prerouting chain, the filter forward chain allows the packet to pass, and the masquerade rule rewrites the reply’s source. The redirect statement is the local-service variant, and the prerouting chain must exist for the framework to match the replies. The conntrack -L command is the verification. The result is a service that is reachable from the internet without exposing the internal network.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!