Source NAT
A source NAT rule has three parts: which packets it matches, where they're going, and what to rewrite them to.
outbound-interface name eth0: only traffic leaving via the WAN.source address 10.0.0.0/24: only traffic from the NAT-LAN. This line matters more than it looks; see below.translation address masquerade: use whatever address eth0 has right now. Masquerade suits a WAN address that might change (DHCP or PPPoE). With a fixed address, you can writetranslation address 203.0.113.2instead.
After the commit, both hosts reach the server, and its log shows the difference: the NAT-LAN host arrives as 203.0.113.2 (R1's WAN address), and the public host still arrives as 198.51.100.2. show nat source translations shows the mapping R1 keeps for each connection, so it can rewrite the replies on the way back. Many private hosts can share the one public address because R1 changes source ports too when it has to (Port Address Translation (PAT), the usual form of NAT).
The classic mistake: no source match
It's tempting to write the rule with just the outbound interface. To see what happens, remove the source line and test from the public LAN again. The public host now appears on the internet as 203.0.113.2: R1 is NATing addresses that are already public. Everything still seems to work, but you've broken the reason for having public addresses: other people's logs and allow-lists see the wrong address, and anything that expects to be reached at 198.51.100.2 gets confused. Always match the private source range.
set nat source rule 100 description 'NAT-LAN to the internet via the WAN address'
set nat source rule 100 outbound-interface name 'eth0'
set nat source rule 100 source address '10.0.0.0/24'
set nat source rule 100 translation address 'masquerade'- Line 2: Only traffic leaving via the WAN.
- Line 3: Only traffic from the private LAN.
- Line 4: Rewrite the source to eth0's current address.
root@lanhost:~# curl -sS -m 5 http://192.0.2.10/
hello from server
root@pubhost:~# curl -sS -m 5 http://192.0.2.10/
hello from server
root@server:~# cat /tmp/http.log
203.0.113.2 - - [17/Oct/2026 00:53:01] "GET / HTTP/1.1" 200 -
198.51.100.2 - - [17/Oct/2026 00:53:01] "GET / HTTP/1.1" 200 -- Lines 1–2: The private host works now.
- Line 6: It arrives as R1's WAN address…
- Line 7: …while the public host still arrives as itself.
vyos@R1:~$ show nat source rules
Rule Source Destination Proto Out-Int Translation
------ ----------- ------------- ------- --------- -------------
100 10.0.0.0/24 0.0.0.0/0 any eth0 masquerade
sport any dport any
vyos@R1:~$ show nat source translations
Pre-NAT Post-NAT Proto Timeout Mark Zone
--------------- ----------------- ------- --------- ------ ------
10.0.0.10:46886 203.0.113.2:46886 tcp 116 0- Lines 4–5: The rule: private source, out of eth0, masquerade.
- Line 9: One live connection: 10.0.0.10 port 46886 became 203.0.113.2 port 46886. The port was free, so it didn't need changing.
root@pubhost:~# curl -sS -m 5 http://192.0.2.10/
hello from server
root@server:~# cat /tmp/http.log
203.0.113.2 - - [17/Oct/2026 00:53:22] "GET / HTTP/1.1" 200 -
vyos@R1:~$ show nat source rules
Rule Source Destination Proto Out-Int Translation
------ --------- ------------- ------- --------- -------------
100 0.0.0.0/0 0.0.0.0/0 any eth0 masquerade
sport any dport any- Line 4: The public host is now hidden behind the WAN address.
- Line 8: Source 0.0.0.0/0: everything leaving eth0 is NATed, public addresses included.
Check your understanding
Why does the source NAT rule match 10.0.0.0/24 instead of everything leaving eth0?
- So the public LAN keeps its own addresses on the internet
- Because masquerade only works with a source match
- To stop the router NATing its own traffic
- It's required syntax