Skip to main content
RunBook Academy

OPNsenseXII · NAT Fundamentals and Outbound NATOutbound NAT

Outbound NAT rule fields — interface, source, destination, translation target

Intermediate⏱ ~13 minpfctl

What you'll learn

  • Read every field in an outbound NAT rule
  • Identify the three common misconfigurations
  • Write rules that translate specific subnets to specific WAN IPs
  • Recognise the difference between translation target and translation pool

Prerequisites

Verified against OPNsense 25.x · FreeBSD 14.x · PF (FreeBSD packet filter) FreeBSD 14.x · Unbound 1.20+ · Kea DHCP OPNsense 25.x plugin · WireGuard in-kernel + OPNsense plugin · strongSwan (IPsec plugin) OPNsense 25.x plugin · OpenVPN 2.6.x · Suricata 7.x · 2026-08-14

Not yet marked complete on this device.

An outbound NAT rule has five fields that determine whether it matches a flow and how the source is rewritten. Reading the rule correctly is the difference between a production deployment that routes traffic the way the operator intended and a deployment that silently sends flows to the wrong WAN or fails to translate them at all.

This lesson covers every field in the rule, the PF syntax it generates, and the three misconfigurations that account for most “my NAT rule does not work” tickets.

The five fields

The GUI form under Firewall → NAT → Outbound → Add has these fields, in order:

  1. Interface. The egress interface the rule applies to. PF only considers the rule when the flow is leaving this interface.
  2. Source. The source address (or subnet, or alias) that triggers the translation. A flow matches the rule only if its source is in this set.
  3. Destination. The destination address that triggers the translation. Often any, but can be restricted to a specific subnet or alias.
  4. Translation / Target. The address the source is rewritten to. Typically a single WAN IP (WAN address) or a pool of addresses.
  5. Translation / Pool options. Whether the translation is sticky (one internal client always uses the same external IP for a given target) or random (load-distributed).

A sixth field — Description — is the operator’s note to themselves. It is not part of the PF rule, but it ends up as a comment in the generated ruleset and is the difference between an auditable rule and an opaque one.

Read-only / Safepfctl -s nat -v
$ pfctl -s nat -v | head -10
@8 nat on igb1 inet from 10.99.0.0/24 to any -> 198.51.100.5
[ rdr-anchor "rdr/*" ] [ Owner: NetOps. VoIP subnet to ISP-B. ]

Illustrative output

Reading the generated PF syntax

Each GUI field maps to a part of the PF nat rule. The mapping:

GUI fieldPF syntax
Interfaceon \<iface\> (or implicit if unset)
Sourcefrom \<cidr\>
Destinationto \<cidr\>
Translation target-> \<address\> or -> (\<iface\>:0)
Pool optionssticky-address or random (in parentheses)

A complete rule from the GUI:

Interface:    WAN_B
Source:       10.99.0.0/24
Destination:  any
Translation:  WAN_B address
Pool options: (none)

Generates:

nat on igb2 inet from 10.99.0.0/24 to any -> (igb2:0)

The (igb2:0) syntax means “the first IPv4 address on the igb2 interface” — the WAN IP. If the WAN has multiple IPs (alias addresses, IP aliases), the operator can specify which one is the translation target by entering it explicitly instead of using the interface shorthand.

Translation target: WAN address vs WAN address(es)

The Translation target field has three modes:

  • Interface address. The single primary IP on the chosen interface. This is the default.
  • WAN address. An alias address on the WAN interface — when the WAN has multiple IPs and you want to use a specific one.
  • Other. A static translation target — typically a different interface’s IP or an alias address on a different subnet.

The mode is set in the GUI as a radio button. The first two are “this WAN’s IP” variants; the third is the escape hatch.

A production example: a firewall with two WANs, where WAN-A has two public IPs (198.51.100.1 and 198.51.100.5). The operator wants the VoIP subnet to use the second IP (198.51.100.5) for SIP/RTP, and other subnets to use the first IP (198.51.100.1). The rule for VoIP uses Translation target = 198.51.100.5 explicitly; the rule for other subnets uses the WAN-A default.

The three common misconfigurations

Wrong interface

The rule targets the wrong egress interface. The flow leaves the firewall on a different interface than the rule assumes, and the rule never matches.

The symptom: outbound traffic from the affected source subnet works (it is being translated by a different rule, often an auto rule), but the operator’s intent is not met — for example, the VoIP traffic is leaving on WAN-A when the rule says WAN-B.

The fix: verify the egress interface with tcpdump -nei \<iface\> on each WAN. The interface the packet leaves on is the interface the rule must target.

Read-only / Safetcpdump on WAN
$ tcpdump -nei igb1 host 203.0.113.50 and port 443 2>/dev/null | head -5
tcpdump: listening on igb1, link-type EN10MB
13:42:11.842819 198.51.100.5.51820 > 203.0.113.50.443: S 1823018421:1823018421(0) win 65535
13:42:11.882145 203.0.113.50.443 > 198.51.100.5.51820: S 2938471238:2938471238(0) ack 1823018422 win 65535

Illustrative output

Wrong source

The rule’s source does not match the actual source of the flows. Common causes: the subnet is wrong, the alias the operator referenced has different IPs than expected, or the source is a network the operator forgot to include.

The symptom: the rule never matches. Outbound traffic from the intended source works (because a more general rule, often an auto rule, catches it), but it is not translated to the target the operator intended.

The fix: check the alias contents (Firewall → Aliases), verify the subnet with pfctl -s state | grep \<source\>, and use a test source IP that is unambiguously in the rule’s source set.

Wrong destination

The rule’s destination is too restrictive. The operator intended “translate this source on all destinations” but specified a specific destination that the test traffic does not match.

The symptom: the rule does not match in testing but matches in production for a subset of flows. The mismatch is silent and frustrating.

The fix: set Destination = any unless there is a specific reason to restrict. The default of any is the right starting point.

Sticky vs random translation

The Pool options field has two choices that matter:

  • Sticky-address. A given internal source IP always maps to the same external IP for a given destination. PF picks an external IP from the pool on the first flow and remembers the binding for the duration of the state.
  • Random. Each flow picks an external IP from the pool randomly, with no binding between flows.

The default for OPNsense is sticky-address when the translation target is a pool, and no-pool-options when the target is a single IP.

Sticky is the right choice for most production cases: it gives consistent external IPs for a given internal client, which matters for content filtering, geolocation, and IP-based access control on the remote side. Random is the right choice when the pool is a load-distribution mechanism and the remote service does not care about source consistency.

A worked example

The requirement: subnet 10.99.0.0/24 (VoIP) must egress WAN-B (igb2, IP 198.51.100.5). All other internal subnets use WAN-A (igb1, IP 198.51.100.1) by default.

In Manual mode, the rules are:

nat on igb2 inet from 10.99.0.0/24 to any -> 198.51.100.5
nat on igb1 inet from 192.0.2.0/24 to any -> (igb1:0)
nat on igb1 inet from 10.10.0.0/24 to any -> (igb1:0)

The VoIP rule is placed first because it is more specific. PF evaluates top-to-bottom; the first match wins. A flow from 10.99.0.5 to 203.0.113.50 matches the first rule and is translated to 198.51.100.5 via igb2. A flow from 192.0.2.50 to 203.0.113.50 skips the first rule (source does not match) and matches the second rule, translating to 198.51.100.1 via igb1.

Verifying a rule works

The verification sequence after writing an outbound NAT rule:

  1. Apply the change. The rule is live.
  2. Inspect with pfctl -s nat. Confirm the rule is in the generated ruleset, with the expected source, destination, and target.
  3. Generate test traffic. From a host in the source subnet, make an outbound connection (e.g. curl https://ifconfig.me).
  4. Capture on the egress interface. tcpdump -nei igb2 host \<remote\> and port 443 shows the post-NAT source. Compare to the expected target.
  5. Verify the return path. Capture on the egress interface for the return traffic. The destination should be the same IP the test connection used as source.

If the captured source is not the expected target, the rule is not matching. Re-check the source field, the destination field, and the rule order.

Summary

  • An outbound NAT rule has five fields: Interface, Source, Destination, Translation target, Pool options.
  • The interface shorthand (igb1:0) translates to the primary IP on that interface. Use an explicit target for alias IPs.
  • The three common misconfigurations are wrong interface, wrong source, and wrong destination. Each has a distinct symptom and fix.
  • Sticky-address binds a (source, destination) pair to a translation; different destinations may see different external IPs for the same source.
  • Place more specific rules above more general rules. PF evaluates top-to-bottom; the first match wins.
  • Verify with pfctl -s nat and tcpdump on the egress interface.

Knowledge check · 4 questions

  1. Q1. A firewall has WAN-A (igb1) with primary IP 198.51.100.1 and alias address 198.51.100.5. An outbound NAT rule uses Translation target = "WAN address" with Interface = WAN-A. Which IP does the rule translate to?

  2. Q2. In PF, outbound NAT rules are evaluated top-to-bottom on the outgoing path, and the first matching rule wins.

  3. Q3. A rule is meant to translate 10.99.0.0/24 to WAN-B. The rule appears in pfctl -s nat, but test traffic from 10.99.0.5 is being translated to WAN-A. Which of the following are likely causes? Select all that apply.

  4. Q4. Sticky-address is enabled on a nat rule with a pool of two external IPs. An internal client at 192.0.2.50 makes two HTTPS connections to the same remote server. Which statement is correct?

Passing score: 75%. Answers are checked in this browser.