Wheelhouse docs

Publish a service: forward, admit, and reach it from inside#

You will end up with one service on your LAN reachable from the internet, on a port you chose, and — if you want it — reachable from inside the LAN on the same public address.

This is the job that goes wrong most often, and it goes wrong the same way every time: the forward is made, the connection is translated, and then the firewall drops it because nothing admits it. A port forward is two rules, and the NAT page makes both.

Before you start#

  • The operator role and a licence.
  • The inside address of the service, and the port it listens on.
  • The uplink the traffic will arrive on.
  • A default-drop ruleset — A default-drop ruleset that admits what you meant. If your Forward chain does not drop by default, the admitting rule below is unnecessary.

Step 1 — Add the forward#

Protect → NAT → Add port forward.

FieldValue
Descriptionhttps-caddy
Interfaceeth1 — the uplink, already chosen
ProtocolTCP
External port443
Forward to192.0.2.10
Port on the deviceempty — the same port

Port on the device is only for a device listening on a different port from the one the internet uses: a forward from 8443 to 443 fills it in, a forward from 443 to 443 leaves it empty. A range works in External port — 8000-8100 — and so does TCP and UDP, which is the default.

Step 2 — Leave “Allow these connections” on#

Under Firewall, Allow these connections is on by default, and it is the reason the forward will work: it adds a Forward rule that accepts connections arriving on the uplink for 192.0.2.10, port 443, TCP.

Three things about that rule:

It matches the inside address, not the public one. The translation happens before filtering, so by the time the Forward chain sees the connection its destination is already 192.0.2.10.

It is in the Forward chain, not Input. The connection passes through the router to another host; Input is for traffic addressed to the router itself.

It takes a free number — the forward's own when that is free in the Forward chain, and never the number of a rule that already exists there.

When a Forward rule already admits these connections — one for the same host on the same uplink — the panel says which one and adds nothing.

Step 3 — Hairpin, when the LAN uses the public name#

Turn on Also reachable on the public address from inside. This is the answer to "it works from my phone on mobile data but not from my desk"; OPNsense calls it NAT reflection.

Choose the Inside interface; the panel reads the uplink's public address itself. It adds two more rules: the same forward for connections arriving from inside, and a source NAT rule that makes those connections come from the router, so the server's answers go back the way they came. Without the second rule the server answers the client directly, the client sees a reply from an address it did not send to, and the connection never completes.

The better answer on most networks: split-horizon DNS#

Rather than making the LAN travel out to the public address and back, give the service a name inside that points straight at its inside address: a DNS record for caddy.example.com with the address 192.0.2.10. LAN clients then resolve it to the server and never touch NAT at all. Authoritative records for local names has the whole procedure. This is why the OPNsense importer does not translate NAT reflection and says split-horizon DNS is usually the better answer.

Step 4 — Apply#

Read the pending changes: the forward, the Forward rule, and — if you took step 3 — the two inside rules, each as one line. Then apply.

Check it worked#

From outside, not from the LAN. A phone on mobile data, a shell somewhere else, or an online port checker:

bash
curl -sv https://203.0.113.2/ 2>&1 | head -5
nc -vz 203.0.113.2 443

On the router, the counters tell you which half is failing. The NAT page shows each forward's packet and byte counters and the Firewall page each rule's:

Forward's counterForward rule's counterMeans
00Nothing arrived. Wrong uplink, wrong port, or the provider blocks it.
rising0The translation works and the connection is being dropped. The Forward rule does not match — check its interface and its destination address.
risingrisingThe router is doing its job. The problem is the server or its own firewall.

From the LAN, if you took step 3:

bash
curl -sv https://203.0.113.2/ 2>&1 | head -5

When it still does not work#

SymptomCheck
Nothing arrives at allIs the port reachable at all? Many providers block 25, 80 and 443 on consumer connections. Test a high port first.
Works from outside, not from the LANThe inside rules are missing, or the public address has changed. See step 3.
Works, then stops after a few daysA DHCP uplink whose address moved, and an inside rule holding the old one.
The forward's counter rises, the rule's does notThe Forward rule's interface or destination does not match. This is the common one.
Two forwards, one worksRule numbers. A lower-numbered rule that matches first wins; look at the order on the NAT page.
It worked before an uplink changeThe forward names an interface. Uplinks — a second uplink needs its own forward.

Undoing it#

Delete what you added: the forward from its panel on the NAT page (Delete port forward), the inside rules from the same page, and the Forward rule from the Firewall page. Or, if the forward was the last thing you applied, roll back one revision.

See also#

Updated 2026-09-17 nat firewall dnat hairpin