VyOSXXXVIII · NAT FundamentalsPort forwarding
Port forwarding — DNAT, translation table, hairpin NAT, firewall rules
What you'll learn
- Configure inbound port forwarding with DNAT on VyOS 1.5 LTS
- Write the forward-chain rules that permit the post-NAT traffic, in the correct interface direction
- Configure hairpin NAT for LAN hosts that need to reach the public IP
- Distinguish port forwarding from 1:1 NAT and identify when each is appropriate
- Diagnose the production failure where the firewall blocks the translated traffic
Prerequisites
Verified against VyOS 1.5.x LTS (circinus) · VyOS 1.4.x (sagitta) — legacy · FRRouting 10.x (VyOS 1.5) · Linux kernel 6.6 LTS (VyOS 1.5 base) · strongSwan 5.9.x (IPsec) · WireGuard 1.0.x (kernel module + userspace tooling) · 2026-08-15
Port forwarding is the DNAT pattern that allows an Internet client to reach a private host behind the router. The router rewrites the destination of the inbound packet from the public IP to the private host’s IP; the private host sees the traffic as if it came from the router.
The port forwarding pattern is the production default for exposing a single service on a private host. The pattern is composed of three parts:
- DNAT rule — rewrites the destination of inbound traffic.
- Firewall rule — permits the post-NAT traffic on the post-NAT port.
- Hairpin NAT (when needed) — allows LAN hosts to reach the public IP of other LAN hosts.
This lesson covers all three parts, the production failure modes, and the discipline for deploying port forwarding in production.
The DNAT rule
The DNAT rule rewrites the destination of inbound traffic. The rule specifies:
- inbound-interface name — the interface the packet arrives on (the WAN interface).
- destination port — the port the Internet client connects to.
- protocol — the protocol (typically tcp).
- translation address — the private host’s IP.
- translation port — the private host’s port (often the same as the destination port).
# Port forward HTTPS to a private host
set nat destination rule 10 inbound-interface name eth0
set nat destination rule 10 destination port 443
set nat destination rule 10 protocol tcp
set nat destination rule 10 translation address 192.168.1.100
set nat destination rule 10 translation port 443
commit
The rule says: any TCP packet arriving on eth0 with destination port 443 has its destination rewritten to 192.168.1.100:443.
The conntrack entry reflects this:
tcp 6 300 ESTABLISHED src=198.51.100.5 dst=203.0.113.10
sport=52012 dport=443 packets=12 bytes=1844 [ASSURED]
src=192.168.1.100 dst=198.51.100.5 sport=443 dport=52012
packets=8 bytes=4096
The first tuple is the packet as the client sent it; the second is the reply the router expects, from the translated host (192.168.1.100) back to the client (198.51.100.5). When the host replies, the packet matches that reply tuple, and the router rewrites its source back to 203.0.113.10 — the public IP the client connected to — before forwarding.
The firewall rule
DNAT runs in the prerouting hook, before the packet reaches the firewall, so the forward chain evaluates the translated destination — 192.168.1.100:443, not 203.0.113.10:443. The VyOS documentation states this plainly: the DNAT translation occurs before traffic traverses the firewall.
The part operators get wrong is the interface direction.
In a forward rule, inbound-interface is the interface
the packet arrived on and outbound-interface is the
interface it leaves by. A packet from the Internet to
a LAN server arrives on the WAN interface and leaves by
the LAN interface, so the rule reads inbound-interface name eth0 and outbound-interface name eth1.
Writing inbound-interface name eth1 describes a packet
that arrived from the LAN — the opposite flow. The rule
then never matches the forwarded traffic, the default
action drops it, and the port forwarding is broken by the
rule that was meant to permit it.
# Permit the forwarded, post-NAT traffic from WAN to the LAN server
set firewall ipv4 forward filter default-action drop
set firewall ipv4 forward filter rule 5 action accept
set firewall ipv4 forward filter rule 5 description "Return traffic for established flows"
set firewall ipv4 forward filter rule 5 state established
set firewall ipv4 forward filter rule 5 state related
set firewall ipv4 forward filter rule 10 action accept
set firewall ipv4 forward filter rule 10 description "Forwarded HTTPS to web server"
set firewall ipv4 forward filter rule 10 inbound-interface name eth0
set firewall ipv4 forward filter rule 10 outbound-interface name eth1
set firewall ipv4 forward filter rule 10 protocol tcp
set firewall ipv4 forward filter rule 10 destination address 192.168.1.100
set firewall ipv4 forward filter rule 10 destination port 443
set firewall ipv4 forward filter rule 10 state new
commit
Rule 10 matches the post-NAT destination because DNAT has already run by the time the forward chain sees the packet. Rule 5 carries the replies, which travel the other way and would not match rule 10’s interface pair.
VyOS also offers a match on the translation itself, which avoids repeating the private address in two places:
set firewall ipv4 forward filter rule 10 connection-status nat destination
A rule with connection-status nat destination accepts
whatever the DNAT rules translated, so the address and
port live in one place. The cost is precision: it permits
every destination-NAT’d flow, not just this one. Use it
when the DNAT rule set is the policy; use the explicit
address and port match when the two are meant to be
reviewed separately.
Hairpin NAT
The hairpin NAT pattern allows a LAN host to reach the public IP of another LAN host. The pattern is required when the LAN host needs to connect to a service that is exposed via port forwarding.
# Without hairpin: LAN host 192.168.1.50 tries to reach 203.0.113.10:443
# 1. The packet is sent to 203.0.113.10:443 (the public IP)
# 2. The router receives the packet on the LAN interface (eth1)
# 3. PREROUTING: DNAT rule rewrites the destination to 192.168.1.100:443
# 4. The router routes the packet to 192.168.1.100 (on the LAN)
# 5. The host (192.168.1.100) replies with src=192.168.1.100
# 6. The reply goes directly to 192.168.1.50 (no NAT on the return)
# 7. 192.168.1.50 receives a SYN/ACK from 192.168.1.100:443
# 8. The TCP handshake fails (the client expected the source to be 203.0.113.10)
The problem: the LAN host receives a SYN/ACK from an unexpected source (the private host’s IP, not the public IP). The TCP handshake fails.
The fix: hairpin NAT. The router SNATs the source of the LAN-originated packet so the destination host sees the router’s IP as the source. The reply goes back to the router; the router uses conntrack to reverse both NATs.
# Hairpin NAT: SNAT the LAN-originated traffic to the router's LAN IP
set nat source rule 30 outbound-interface name eth1
set nat source rule 30 source address 192.168.1.0/24
set nat source rule 30 destination address 192.168.1.100
set nat source rule 30 destination port 443
set nat source rule 30 protocol tcp
set nat source rule 30 translation address 192.168.1.1
commit
The rule says: any TCP packet from 192.168.1.0/24 destined for 192.168.1.100:443 has its source rewritten to 192.168.1.1 (the router’s LAN IP).
The conntrack entry for hairpin traffic has both SNAT and DNAT applied:
tcp 6 300 ESTABLISHED src=192.168.1.50 dst=203.0.113.10
sport=52012 dport=443 packets=12 bytes=1844 [ASSURED]
src=192.168.1.100 dst=192.168.1.1 sport=443 dport=52012
packets=8 bytes=4096
The reply tuple reads src=192.168.1.100 dst=192.168.1.1:
the server answers from its own address to the router’s
LAN address, which is where the hairpin SNAT put the
client. The router matches the reply against this entry
and reverses both translations, so the packet reaches
192.168.1.50 with a source of 203.0.113.10.
sequenceDiagram
participant C as LAN client (192.168.1.50)
participant R as Router
participant H as Web server (192.168.1.100)
C->>R: SYN dst=203.0.113.10:443
Note over R: PREROUTING DNAT<br/>dst becomes 192.168.1.100:443
Note over R: POSTROUTING hairpin SNAT<br/>src becomes 192.168.1.1
R->>H: SYN src=192.168.1.1 dst=192.168.1.100:443
H->>R: SYN/ACK src=192.168.1.100:443 dst=192.168.1.1
Note over R: conntrack lookup<br/>dst becomes 203.0.113.10<br/>src becomes 192.168.1.50
R->>C: SYN/ACK src=203.0.113.10:443 dst=192.168.1.50
Production failure modes
The five production failure modes the operator must recognise:
- Missing firewall rule — the DNAT rule is in place but the firewall blocks the post-NAT traffic. The port forwarding is silently broken.
- Missing hairpin NAT — LAN hosts cannot reach the public IP of other LAN hosts. The TCP handshake fails with an unexpected source address.
- Wrong translation port — the DNAT rule maps external port 443 to internal port 8443. The operator forgets to configure the firewall rule for 8443. The traffic is DNAT’d but the firewall blocks it.
- Stale conntrack entry — the DNAT rule is changed (e.g., the translation address is moved to a new host) but existing flows still have the old conntrack entry. The new packets use the new translation; the existing flows use the old. The fix is to delete the affected entries, not the whole table.
- Asymmetric routing — the inbound traffic goes through the router but the return path goes directly to the client (bypassing the router). The conntrack entry is not consulted; the return packet has the wrong source address.
Operational commands
The port forwarding state is exercised through the standard VyOS operational commands:
# Show the DNAT rules
show nat destination rules
# Show the forward chain, with per-rule packet counters
show firewall ipv4 forward filter
# Show the conntrack entries
show conntrack table ipv4
sudo conntrack -L -n
# Show the firewall statistics
show firewall statistics
# Live packet capture on the WAN interface
monitor traffic interface eth0 filter 'port 443'
# Live packet capture on the LAN interface
monitor traffic interface eth1 filter 'port 443'
# Delete only the flows for this service, after a DNAT change
sudo conntrack -D -p tcp -d 203.0.113.10 --dport 443
The per-rule counters in show firewall ipv4 forward filter are the fastest way to tell a missing rule from a
misdirected one. A rule that never matches has a counter
of zero while the default-action counter climbs.
Rollback
The port forwarding changes are rolled back the same way as any VyOS configuration change:
# Show the candidate diff
compare
# Revert to a previous revision
rollback 1
commit
save
# Or delete the rule and re-commit
delete nat destination rule 10
commit
save
# Drop only the flows the old rule was translating
sudo conntrack -L -p tcp -d 203.0.113.10 --dport 443
sudo conntrack -D -p tcp -d 203.0.113.10 --dport 443
The discipline: a port forwarding change is followed by a
targeted removal of the flows that rule was translating,
listed with conntrack -L first so the operator can see
what the delete will take with it. Flows that the change
did not touch are left alone.
Production discipline
Cross-course references
- Part XXXVIII-01 (
XXXVIII-VyOS-NAT/ SNAT vs DNAT concept) covers the underlying NAT primitives. - Part XXXVIII-04 (
XXXVIII-VyOS-NAT/ one-to-one) covers the 1:1 NAT pattern (different from port forwarding). - Part XXXVII-05 (
XXXVII-VyOS-Firewall/ default deny) covers the firewall rules that permit post-NAT traffic. - Part XXXVIII-06 (
XXXVIII-VyOS-NAT/ NAT troubleshoot) covers the diagnostic method.
Quiz
Knowledge check · 5 questions
Q1. An operator configures DNAT to forward HTTPS to a private host. The DNAT rule is in place. The port forwarding does not work — the Internet client times out. What is the most likely cause?
Q2. eth0 is the WAN interface and eth1 is the LAN interface. A DNAT rule forwards TCP 443 from the public IP to 192.168.1.100. Which interface match belongs on the `forward` rule that permits the forwarded traffic?
Q3. Hairpin NAT is required when LAN hosts need to reach the public IP of other LAN hosts (via port forwarding).
Q4. An operator configures port forwarding for HTTPS. The Internet client can reach the service. A LAN client (192.168.1.50) tries to reach the service via the public IP (203.0.113.10:443) but the TCP handshake fails. What is the cause, and what is the fix?
DNAT rule: inbound-interface name eth0, destination port 443, translation address 192.168.1.100:443. No hairpin NAT rule. The LAN client 192.168.1.50 sends a SYN to 203.0.113.10:443. The router rewrites the destination to 192.168.1.100:443. The host (192.168.1.100) replies directly to 192.168.1.50 with src=192.168.1.100. The client receives a SYN/ACK from 192.168.1.100:443, not from 203.0.113.10:443.
Q5. An operator changes the port forwarding translation address from 192.168.1.100 to 192.168.1.200 (moving the service to a new host). Existing connections to the service are still routed to the old host (192.168.1.100). What is the cause, and what is the fix?
Before the change: DNAT rule maps 203.0.113.10:443 to 192.168.1.100:443. Existing conntrack entries have the reply direction with dst=192.168.1.100. After the change: DNAT rule maps 203.0.113.10:443 to 192.168.1.200:443. New conntrack entries have dst=192.168.1.200. Existing connections still use the old conntrack entry.
Passing score: 75%. Answers are checked in this browser.