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.
| Field | Value |
|---|---|
| Description | https-caddy |
| Interface | eth1 — the uplink, already chosen |
| Protocol | TCP |
| External port | 443 |
| Forward to | 192.0.2.10 |
| Port on the device | empty — 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:
curl -sv https://203.0.113.2/ 2>&1 | head -5
nc -vz 203.0.113.2 443On 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 counter | Forward rule's counter | Means |
|---|---|---|
| 0 | 0 | Nothing arrived. Wrong uplink, wrong port, or the provider blocks it. |
| rising | 0 | The translation works and the connection is being dropped. The Forward rule does not match — check its interface and its destination address. |
| rising | rising | The router is doing its job. The problem is the server or its own firewall. |
From the LAN, if you took step 3:
curl -sv https://203.0.113.2/ 2>&1 | head -5When it still does not work#
| Symptom | Check |
|---|---|
| Nothing arrives at all | Is 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 LAN | The inside rules are missing, or the public address has changed. See step 3. |
| Works, then stops after a few days | A DHCP uplink whose address moved, and an inside rule holding the old one. |
| The forward's counter rises, the rule's does not | The Forward rule's interface or destination does not match. This is the common one. |
| Two forwards, one works | Rule numbers. A lower-numbered rule that matches first wins; look at the order on the NAT page. |
| It worked before an uplink change | The 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#
- A default-drop ruleset that admits what you meant
- Use groups instead of literals — for the fifth forward to the same host
- Authoritative records for local names — the split-horizon answer
- Masquerade behind each uplink — publishing on a second uplink
- NAT — port forwards and Firewall — rules