Wheelhouse docs

Source NAT#

Source NAT rewrites the source address of traffic leaving the router, which is what lets a network of private addresses reach the internet. The Source NAT table on the NAT page lists every rule under nat source rule <n> with its live counters, and clicking a row opens an editor for it. There is no + Add source NAT button here: new source rules come from the installer, from the hairpin option on a port forward, from the Uplinks page, or from the CLI. This page covers all four, and what the editor can do to what they wrote.

The table#

ColumnFrom
#The rule number.
Descriptiondescription.
Outoutbound-interface name, or any.
Sourcesource address, or any.
Translationtranslation address, or the word masquerade when the rule sets none.
Packets / Bytesshow nat source statistics, keyed by rule number.

masquerade is the value most rules carry: it means "use whatever address the outbound interface currently has", which is the only thing that works on a DHCP or PPPoE uplink. A fixed value translates to that address instead.

What the installer writes#

When the installer's NAT box is left checked and there is both a WAN and a LAN with an address, it writes exactly one rule:

set nat source rule 100 description "LAN to WAN"
set nat source rule 100 outbound-interface name eth1
set nat source rule 100 source address 10.0.10.0/24
set nat source rule 100 translation address masquerade

The source address is the LAN's network, derived from the LAN interface's address. A second segment — a guest VLAN, a lab network — is not covered by that rule and will leave with private addresses until it has one of its own (packaging/wheelhouse-install).

The Uplinks page is where a second uplink gets its masquerade. Two places offer it:

  • The add-uplink wizard has a checkbox, Masquerade behind it (nat source rule N), with the hint that without it traffic sent that way leaves with private addresses.
  • An uplink that has no source NAT rule gets a panel offering to stage one.

Both stage the same three operations, at the next free source rule number:

set nat source rule 110 outbound-interface name eth2
set nat source rule 110 translation address masquerade
set nat source rule 110 description 'Wheelhouse: masquerade behind eth2'

The number is computed from the existing rules: 100 when the highest existing rule is below 100, and ten past the highest when it is 100 or more (agent/wan.go sourceNATByInterface).

The Uplinks table's NAT column names the rule and its translation for each uplink, and links here to edit it. The uplink model treats a rule as that uplink's NAT when it names the uplink as its outbound interface, sets a translation and is not disabled; a masquerade rule wins over a fixed one when an uplink has both.

The editor#

Clicking a source row opens the same panel the destination rules use, relabelled for the direction:

FieldCommand
Outbound interfaceset nat source rule <n> outbound-interface name <iface>
Protocolset nat source rule <n> protocol <tcp\|udp\|tcp_udp\|icmp>
Source addressset nat source rule <n> source address <addr-or-cidr>
Source portset nat source rule <n> source port <port>
Translation addressset nat source rule <n> translation address <addr-or-masquerade>
Translation portset nat source rule <n> translation port <port>
Log matchesset nat source rule <n> log
Disabledset nat source rule <n> disable
Descriptionset nat source rule <n> description <text>

Clearing a field stages the matching delete. Delete rule stages delete nat source rule <n>.

Writing one by hand#

There is no add editor, so a source rule that the wizards do not cover is typed. From the console, a second segment behind the same uplink:

set nat source rule 120 description 'guest to WAN'
set nat source rule 120 outbound-interface name eth1
set nat source rule 120 source address 10.0.20.0/24
set nat source rule 120 translation address masquerade

Or through the API, staged and committed the way the UI would do it:

bash
curl -sk -X POST https://<router>:8443/api/stage \
  -H "Authorization: Bearer wh_…" -H 'Content-Type: application/json' \
  -d '[{"op":"set","path":["nat","source","rule","120","outbound-interface","name","eth1"]},
       {"op":"set","path":["nat","source","rule","120","source","address","10.0.20.0/24"]},
       {"op":"set","path":["nat","source","rule","120","translation","address","masquerade"]}]'
curl -sk -X POST https://<router>:8443/api/commit \
  -H "Authorization: Bearer wh_…" -H 'Content-Type: application/json' \
  -d '{"confirm_minutes":2}'

Either way the rule appears in this table on the next read, with counters.

See also#

  • NAT — the page these tables live on.
  • Port forwards — the hairpin option stages a source rule too.
  • Worked rulesets — a segment that needs both a filter rule and a translation.
  • Uplinks — the page that offers a per-uplink masquerade rule.
  • Authenticating — the header the API example above uses.

Checked against#

ui/src/pages/Nat.tsx, ui/src/pages/Wan.tsx, agent/wan.go, agent/main.go, packaging/wheelhouse-install.

Updated 2026-09-02 manual nat snat masquerade