OPNsenseLab
Flat isometric illustration of a teal network splitter box with three RJ45 jacks and two grey patch cables plugged in, on a white pad.
Firewall

OPNsense Port Forwarding: NAT Rules That Actually Work

OPNsense port forwarding needs a destination NAT rule plus a filter rule that matches the internal IP. Here is the correct order and the hairpin fix.

By OPNsenseLab Editorial · ·Updated August 22, 2026 · 11 min read

Almost every “my OPNsense port forward doesn’t work” thread ends the same way: the NAT rule was fine, and the filter rule was wrong. OPNsense splits the job into two objects that live on two different pages, and the second one references an address the first one just changed. Get that relationship right and port forwarding stops being mysterious.

This guide covers destination NAT (the thing most people mean by “port forwarding”), the filter rule that has to accompany it, hairpin access from inside your own network, and the two situations where the default settings actively work against you: multi-WAN and VPN interfaces.

The one rule that explains every failure

NAT is evaluated before the packet filter. By the time your firewall rules see the packet, the destination address and port have already been rewritten.

That single ordering fact produces the rule everyone gets wrong: the pass rule must match the internal address and the internal port, not the public IP and not the external port. A rule that says “allow WAN traffic to my public IP on TCP 443” will never match, because no packet with that destination survives the NAT stage.

Three terms cover everything on the OPNsense NAT pages, and mixing them up is why forum answers often do not apply to the question:

TermWhat it changesWhere it lives
DNAT (destination NAT)Destination IPFirewall → NAT → Destination NAT (Port Forward)
PAT (port address translation)Destination portSame rule, “Redirect target port”
SNAT (outbound / source NAT)Source IPFirewall → NAT → Outbound

Port forwarding is DNAT, usually with PAT alongside it. Outbound NAT only matters here when you need hairpin access, which is covered further down.

Worth stating plainly, because the documentation does too: NAT is not a security control. It is address translation. Everything that decides whether a connection is allowed happens in the filter rules, and the field-by-field walkthrough in OPNsense firewall rules explained for beginners is the prerequisite for the rest of this page.

Before you create anything

Three checks save most of the debugging time:

  1. The internal host needs a fixed address. A DHCP lease that moves breaks the forward silently. Use a static mapping in Services → DHCPv4 → [interface] → Static Mappings, or configure the address on the host itself.
  2. Confirm you have a routable public IP. If your WAN address is in 100.64.0.0/10, you are behind carrier-grade NAT and no port forward on your firewall can work — the ISP is doing the translation upstream. Compare the WAN interface address on the dashboard with what a “what is my IP” service reports. If they differ, stop here and ask the ISP for a public address or use an outbound tunnel instead.
  3. Decide whether you should be forwarding at all. Publishing a management interface, RDP, SMB, or a database port to the open internet is the most common way homelab machines get compromised. Remote administration belongs behind a WireGuard road-warrior tunnel, not behind a port forward.

Step 1: Build the alias first

Firewall → Aliases → Add. Create a host alias, for example srv_jellyfin pointing at 192.168.10.50, and a port alias if you like, for example port_jellyfin = 8096.

This is not busywork. Aliases mean the address appears in exactly one place, so when the server moves you edit one object rather than hunting for every rule that hardcoded the address. It also makes the rule list readable six months later, which is when you will actually need it.

Step 2: Create the destination NAT rule

Firewall → NAT → Destination NAT (Port Forward) → Add. The fields that matter:

FieldValueWhy
InterfaceWANWhere the traffic arrives
TCP/IP VersionIPv4IPv6 does not use NAT for this; use a plain filter rule
ProtocolTCPMatch the service, not “any”
Sourceany, or a restricted aliasSee the hardening note below
DestinationWAN addressThe public side of the translation
Destination port range8096 (or your external port)What the outside world connects to
Redirect target IPsrv_jellyfinThe alias from step 1
Redirect target port8096The port the service actually listens on
DescriptionSomething you will recogniseFuture you is the audience

Using a different external port from the internal one is fine and often sensible — it drops a large share of untargeted scanning noise. It is obfuscation, not security, so treat it as a way to reduce log volume rather than as a control.

Step 3: Pick the right filter rule association

This is the setting the failures cluster around, and it is also where guides disagree with each other for a reason nobody explains: there are two rule engines, and they use different names for the same dropdown. Check which page you are on before you follow anyone’s instructions.

On the classic Firewall → Rules pages, Filter rule association offers four choices:

ChoiceWhat it does
Add associated filter ruleCreates a linked pass rule in Firewall → Rules and keeps it in sync when you edit the NAT rule. The sane default.
Add unassociated filter ruleCreates the rule once and then leaves it alone, so you can edit it freely. Nothing keeps it in sync afterwards.
PassPasses the traffic on the NAT rule itself, with no rule in the list at all. Convenient, and exactly the kind of invisible state that makes a firewall hard to audit later.
NoneYou write the pass rule yourself.

On the newer Firewall → Rules [new] engine the same field has three, and the documentation describes these:

ChoiceWhat it doesClassic equivalent
PassA filter rule is added and maintained automatically; it cannot be seen or edited in the rules list. Documented as the recommended choice for most setups.Pass
Register ruleAdds a linked rule that is visible in the rules list and updates with the NAT rule. Still not hand-editable.Add associated filter rule
ManualNo linked rule; you write it yourself. Documented as the choice for complex setups, naming port forwards on VPN interfaces specifically, because it is the only one that lets you edit the rule and turn reply-to off.None

One trap worth knowing on the classic page: if you save a rule with None or Pass, the associated-rule options disappear from the dropdown on subsequent edits, and getting them back means recreating the NAT rule. Choose the association when you create the rule, not afterwards.

If you write the rule by hand, put it on the WAN interface with:

Action:      Pass
Interface:   WAN
Protocol:    TCP
Source:      any (or your restricted alias)
Destination: srv_jellyfin        <- the INTERNAL alias
Dest port:   8096                <- the INTERNAL port

Destination is the internal host. That is the whole trick.

If you would rather generate this pair from a form than hand-type it, the site’s OPNsense rule and alias recipe generator has a “safe inbound port-forward” recipe that emits the alias, the NAT rule, and the matching pass rule in creation order, with an inline check for the missing-pass-rule mistake.

Step 4: Restrict the source

“Source: any” means the entire internet, including every scanner that indexes exposed services within hours of them appearing. Where the client population is known, restrict it:

  • A GeoIP alias limiting the forward to your own country
  • A URL-table alias of your office or family members’ static IPs
  • A threat-feed alias in the block direction, above the pass rule

None of these replace authentication on the service itself, but each one cuts the volume of unsolicited traffic that ever reaches the internal host.

Step 5: Test it from outside

Testing a port forward from a LAN client is the second-most common source of false failure reports. From inside, the packet never traverses the WAN interface, so the NAT rule does not apply. Test from a phone on mobile data with Wi-Fi off, or from any host genuinely outside your network.

When it does not work, Firewall → Log Files → Live View, filtered to the destination port, answers the question in seconds. Three readings:

What the log showsWhat it means
Nothing at allTraffic is not arriving — CGNAT, upstream ISP filtering, or wrong WAN interface on the rule
A block on WAN against the internal IPNAT worked, the pass rule is missing or wrong
A pass, but the client still times outThe internal host is refusing: service not listening, host firewall, or wrong port

Hairpin NAT: reaching your own service by its public name

Once the forward works from outside, someone on the LAN will try https://example.com and get nothing. This is normal. The client resolves the public IP, sends the packet to the firewall, and the firewall treats itself as the destination because that address belongs to its WAN interface.

There are two families of fix, and the one to try first is not in the OPNsense how-to at all.

Split-horizon DNS. Add a host override in Services → Unbound DNS → Overrides so internal clients resolve example.com straight to the internal address. No NAT involved, no extra state, nothing to debug, and it costs one record. Reach for this first; the DNS blocklist and Unbound guide covers the overrides page in more detail. It stops being sufficient when clients cannot be made to use your resolver, or when the name has to resolve identically for something that queries an external server.

Reflection NAT, when DNS cannot solve it. The reflection and hairpin NAT how-to documents three methods, differing only in how much of the work is automatic:

MethodDestination NATOutbound NATFilter rules
1manualmanualautomatic
2automaticmanualmanual
3automaticautomaticmanual

The distinction the how-to draws is the one worth carrying away. If the client and the server sit on different segments they cannot reach each other by ARP, the reply comes back through the firewall anyway, and destination NAT alone is enough — that is reflection. If they sit on the same segment they can reach each other directly, the server would answer the client straight from its own interface with the wrong source address, and the connection dies. That case needs outbound NAT as well so the reply is forced back through the firewall. That second case is what “hairpin” strictly means, and it is why the internet’s advice on this contradicts itself: people are describing two different topologies with one word.

If your reason for wanting hairpin is that you host several services on one address, a reverse proxy is usually the better structure: HAProxy on OPNsense with Let’s Encrypt puts one HTTPS front end in front of everything and turns a pile of port forwards into a single 443 forward plus hostname routing.

The two cases where defaults betray you

Multi-WAN

On a firewall with two uplinks, an automatically generated filter rule carries reply-to, which forces the response out of the gateway the packet arrived on. That is correct behaviour and it is what keeps asymmetric routing from breaking inbound connections — but it also means a forward created on WAN1 will not answer traffic arriving on WAN2. Each uplink needs its own NAT and filter rule pair, and dynamic DNS has to point at whichever address is currently in service. The mechanics of the failover itself are in OPNsense multi-WAN failover with gateway groups.

Port forwards on VPN interfaces

reply-to is the reason port forwards on a WireGuard or OpenVPN interface behave strangely: the tunnel has no gateway in the sense the rule expects. This is precisely why the documentation recommends filter rule association Manual on VPN interfaces (None on the classic pages) — write the pass rule yourself and disable reply-to in its advanced options, which is the one thing an automatically maintained rule will not let you do.

Common failure table

SymptomUsual cause
Works from outside, not from LANMissing hairpin — use split-horizon DNS
Log shows block against internal IPPass rule targets the public IP instead of the internal one
Nothing in the log at allCGNAT, or the rule is on the wrong interface
Worked, then stoppedInternal host’s DHCP lease changed — needs a static mapping
Works on one uplink onlyMulti-WAN: the second uplink has no rule pair
Intermittent on a VPN interfacereply-to on an auto-generated rule

When a port forward is the wrong answer

If the goal is your own access to your own services, a VPN is simpler, safer, and less work: one forward for the tunnel, then everything else stays private. If the goal is publishing several web services, a reverse proxy consolidates them behind one certificate and one entry point. Port forwarding earns its place when a specific external party needs a specific service on a specific port — a game server, an inbound webhook, a mail relay — and even then it should be source-restricted and segmented onto its own VLAN, which the OPNsense VLAN configuration guide covers in the DMZ pattern.

New to the platform? Start with the OPNsense initial setup guide for interface assignment and management-plane hardening, then come back here. Running the same design on the other BSD firewall? pfSenseLab documents the equivalent NAT workflow there.

Sources

  1. Network Address Translation — OPNsense documentation
  2. Reflection and Hairpin NAT — OPNsense documentation
  3. Aliases — OPNsense documentation
  4. Firewall rules — OPNsense documentation
#opnsense #nat#port-forwarding #firewall #networking

Related