pjhtech Tools
MikroTik

MikroTik Port Forward Works Externally but Fails from LAN

Fix same-subnet hairpin NAT with a LAN destination rule and a scoped return-path translation; check external forwarding separately.

If port forwarding works from the Internet but fails from the LAN, the WAN-only destination-NAT rule does not match that LAN request. Add a LAN destination-NAT rule and, when the server would reply directly to a same-subnet client, the scoped hairpin source-NAT rule below.

LAN access to the public address: hairpin checks

ObservationCause to investigate
LAN request never matches destination NATWAN-only interface match or incorrect public destination/protocol
Same-subnet server replies directly to clientReply bypasses the router that must reverse NAT
Routed server replies through the router alreadyCheck policy and route before adding source NAT
Old sessions retain old behaviorConnection tracking retains first-packet NAT decisions

For same-subnet direct replies, use split DNS where the internal address/port suits the application, or narrowly scoped hairpin source NAT. Split DNS does not translate external port 8443 into internal port 443. Hairpin source NAT also hides the client’s source from the server. Hairpin packet flow.

Example: br-lan is 10.42.50.1/24, client 10.42.50.10 connects to public 192.0.2.18:8443, and server 10.42.50.20:443 uses gateway 10.42.50.1. Replace the public address. Read /ip/firewall/filter/print detail where chain=forward and replace <forward-drop-id> with the blocking rule’s number. Keep existing established/related reply permits and deliberate restrictions above the new permit.

Configuration change • adapt the example identifiers
/ip/firewall/nat
add chain=dstnat in-interface=br-lan src-address=10.42.50.10 dst-address=192.0.2.18 protocol=tcp dst-port=8443 action=dst-nat to-addresses=10.42.50.20 to-ports=443 comment="LAN hairpin destination"
add chain=srcnat out-interface=br-lan src-address=10.42.50.10 dst-address=10.42.50.20 protocol=tcp dst-port=443 action=src-nat to-addresses=10.42.50.1 comment="LAN hairpin return via router"
/ip/firewall/filter/add chain=forward action=accept in-interface=br-lan out-interface=br-lan src-address=10.42.50.10 dst-address=10.42.50.20 protocol=tcp dst-port=443 connection-nat-state=dstnat place-before=<forward-drop-id> comment="Allow LAN hairpin HTTPS"

Now the server replies to 10.42.50.1, so the router can reverse both translations. The same source-NAT rule also matches direct-address HTTPS if that traffic is routed through this router. If preserving that direct source identity is required, use split DNS and the internal service port instead. This example grants no additional external source access.

Scope: RouterOS IPv4 source NAT, restricted port forwarding and hairpin NAT.

LAN clients have no Internet: correct source NAT

For a changing WAN address, masquerade selects the egress address. For a fixed public address, explicit src-nat keeps the intended translation clear. These examples are alternatives for the sample LAN; do not add both:

The sample LAN is 10.42.50.0/24 on br-lan; its router is 10.42.50.1. The WAN interface list must contain the actual routed uplink. Check it with /interface/list/member/print. 192.0.2.18 is a documentation address; replace it with the assigned public WAN address. Run /ip/firewall/nat/print detail before adding a rule. Keep VPN/no-NAT exceptions ahead of general source NAT and edit an existing matching rule instead of duplicating it. NAT behavior.

RouterOS 7
/ip/firewall/nat/add chain=srcnat src-address=10.42.50.0/24 out-interface-list=WAN action=masquerade
RouterOS 7
/ip/firewall/nat/add chain=srcnat src-address=10.42.50.0/24 out-interface-list=WAN action=src-nat to-addresses=192.0.2.18

The forward also fails externally: correct its mapping and permit

Example: allow only 198.51.100.44 to connect to WAN address 192.0.2.18 TCP 8443, translated to 10.42.50.20:443. The server uses gateway 10.42.50.1. Replace both documentation public addresses.

First run /ip/firewall/filter/print detail where chain=forward and identify the drop that would block this new connection. Substitute that rule’s number for <forward-drop-id>; do not append a permit below the drop. Existing established,related reply handling and invalid-state drops stay above it. The filter sees the translated destination and port:

RouterOS 7
/ip/firewall/nat/add chain=dstnat in-interface-list=WAN dst-address=192.0.2.18 src-address=198.51.100.44/32 protocol=tcp dst-port=8443 action=dst-nat to-addresses=10.42.50.20 to-ports=443 comment="Restricted service publication"
RouterOS 7
/ip/firewall/filter/add chain=forward action=accept in-interface-list=WAN src-address=198.51.100.44/32 connection-nat-state=dstnat protocol=tcp dst-address=10.42.50.20 dst-port=443 place-before=<forward-drop-id> comment="Allow restricted published service"

Check that no earlier destination-NAT rule captures the same public address and port. If one does, edit that mapping or insert the new mapping before it using place-before=<nat-rule-id>. Do not move a permit above an intentional source restriction.

Test from the permitted external source. Read /ip/address/print and compare the WAN address with the address your ISP assigns for inbound traffic. A private/100.64.0.0/10 WAN needs an upstream mapping; ISP CGNAT requires a reachable public service from the ISP.

On a Linux server, ss -lnt must show TCP 443 listening on 10.42.50.20 or a wildcard, and ip route get 198.51.100.44 must send the reply through 10.42.50.1. A listener bound only to 127.0.0.1 cannot receive the forward.

Inspect counters and retest a fresh session

RouterOS 7
/ip/firewall/nat/print stats
/ip/firewall/filter/print stats
/ip/firewall/connection/print detail

Close the test connection after a NAT edit and reconnect. Look for an increase in packets on the expected NAT rule and forward permit. In the connection list, identify the external test by client 198.51.100.44 and destination 192.0.2.18:8443. Its reply source (reply-src-address and port) must be 10.42.50.20:443.

For the hairpin test, reply-dst-address must be 10.42.50.1. Confirm that the client’s HTTPS request completes. Close only the test connection before retrying; clearing the whole connection table interrupts other users.

References