MikroTik Guide
A pink isometric wireless router with three raised antennas sits on a glowing pink diamond-shaped platform, its front LED indicator row lit and two side cables glowing at the ends.
Troubleshooting

MikroTik Port Forwarding Not Working: Six Checks

This guide diagnoses MikroTik port-forward failures through dst-nat counters, firewall order, hairpin NAT, host gateways, CGNAT, and connection tracking.

By MikroTik Guide Editorial · · 6 min read

MikroTik port forwarding not working comes down to one of six things, and the fastest way through them is to check in the order RouterOS processes the packet. The Packet Flow in RouterOS diagram puts dst-nat in prerouting, before the routing decision and before the filter forward chain. So the dst-nat rule sees the packet first, the forward chain sees it already rewritten to the private address, and the reply has to come back through the same router. Each check below maps to one stage.

What a working forward looks like on a default config

On a router that still carries the default firewall, a port forward is one rule:

/ip firewall nat
add chain=dstnat action=dst-nat protocol=tcp dst-port=443 in-interface-list=WAN \
    to-addresses=192.168.88.10 to-ports=443 comment="fwd https to web01"

That is the shape of the example in MikroTik’s NAT documentation, which forwards TCP 22 on 172.16.16.1 to 10.0.0.3. Nothing else is needed on a defconf box because of one filter rule that Building Advanced Firewall lists verbatim:

add action=drop chain=forward comment="defconf: drop all from WAN not DSTNATed" \
    connection-nat-state=!dstnat connection-state=new in-interface-list=WAN

Read the negation. New connections from WAN are dropped unless a dst-nat rule already claimed them, so a forward needs no separate accept. If you replaced the default firewall with your own and it ends in a plain chain=forward action=drop, add the mirror image above the drop and below the established,related accept:

add chain=forward action=accept connection-nat-state=dstnat connection-state=new in-interface-list=WAN

Order matters; the firewall rules explainer covers why.

Check 1: does the dst-nat counter move?

From a phone on LTE, hit the port. Then on the router:

/ip firewall nat print stats

If the packet counter on the dst-nat rule stays at zero, the packet never matched. Most common first:

  • Wrong ingress interface. On a PPPoE WAN the public address lives on pppoe-out1; ether1 only carries PPPoE frames, so in-interface=ether1 never matches. Use in-interface-list=WAN and confirm membership with /interface list member print, as in the PPPoE client guide.
  • Hard-coded dst-address= on a dynamic WAN. The address changed, the rule died. dst-address-type=local matches any address assigned to a router interface, per the matchers reference, and survives the change.
  • Wrong protocol. WireGuard, most game servers and DNS are UDP. A protocol=tcp forward for a UDP service matches nothing.
  • A broader dst-nat rule above yours. NAT evaluates only the first packet of a connection and connection tracking applies the result to the rest, so the first matching rule wins.

Counter at zero with a correct-looking rule? Sniff the WAN before blaming the config: /tool sniffer quick interface=pppoe-out1 port=443. No SYN arriving means the problem is upstream (Check 5).

Check 2: the forward chain and rule shadowing

Counter moves, still no service? Now the forward chain:

/ip firewall filter print stats chain=forward

Watch the defconf drop rule during the test. Two edits break it. Someone removes connection-nat-state=!dstnat “to tighten things up”, which turns it into drop-everything-from-WAN. Or a custom accept for the port sits below a drop-all and is shadowed, with zero hits forever.

One trap for diagnostics rather than traffic: the default fasttrack-connection rule. The packet-flow page states that FastTrack packets bypass the firewall and connection tracking, so once a forwarded connection is established your filter counters stop moving even though it works. Judge the forward by the dst-nat counter and the connection table, not by later filter hits.

Check 3: you are testing from inside (hairpin NAT)

The most common false alarm. A LAN client at 192.168.88.50 opens a connection to the public IP. dst-nat rewrites the destination to 192.168.88.10; the source is untouched. web01 replies directly to .50 across the switch, the reply carries .10 as its source, the client expected the public IP, and the connection is dropped. The forward “works from outside, not from inside”.

RFC 4787 REQ-9 says a NAT MUST support hairpinning. RouterOS does, but only when you add the srcnat rule that forces replies back through the router. MikroTik’s NAT page gives the pattern; with the addresses above:

/ip firewall nat
add chain=srcnat action=masquerade src-address=192.168.88.0/24 dst-address=192.168.88.10 \
    protocol=tcp dst-port=443 comment="hairpin https"

The dst-nat rule must also match LAN-originated packets, which arrive on the bridge, not on in-interface-list=WAN. A June 2025 forum thread on a hAP ax2 running RouterOS 7.17 ended with exactly that fix: replace the interface-list match with dst-address-type=local, add the masquerade, and let the forward chain accept dst-natted traffic from LAN too. Trade-off: web01’s logs now show every hairpinned client as the router’s LAN address. Split DNS, returning 192.168.88.10 for the hostname inside, avoids that and is the cleaner design.

Check 4: the host behind the forward

A January 2024 thread on an RB750Gr3 with new VLANs is the canonical case. Sniffing showed packets reaching 192.168.10.150 on the right port and nothing coming back. The router was fine; every target device had the wrong default gateway. A host that cannot route back to the client’s public address cannot answer a forward.

Check the tracked connection:

/ip firewall connection print where dst-address~"192.168.88.10:443"

The connection tracking page documents the dstnat flag on an entry that went through dst-nat. An entry with that flag that never reaches established means the router did its job and the host did not. Check the host’s default gateway, its firewall profile (Windows on “Public”, ufw on Ubuntu), whether the service binds 127.0.0.1 instead of 0.0.0.0, and which VLAN it actually sits in.

Check 5: the ISP is in front of you

If nothing arrives on the WAN interface, look at the address on it:

/ip address print where interface=pppoe-out1

An address inside 100.64.0.0/10 is Shared Address Space, reserved by RFC 6598 for carrier-grade NAT. A 10.0.0.0/8 or 192.168.0.0/16 WAN address means an ISP modem in router mode, so you are double-NATed. Either way the SYN dies before your dst-nat rule can match it. Under CGNAT there is no fix on the MikroTik; the options are a static IP from the ISP, or a WireGuard tunnel to a small VPS that forwards the port down to you. The WireGuard configuration guide covers the RouterOS side, and the NAT traversal explainer covers why the tunnel works where the forward cannot. Under double NAT, bridge the modem or forward the port on it to the MikroTik’s WAN address. Some residential plans also block inbound TCP 25, 80 or 443; a non-standard port that works while 443 fails is the tell.

Check 6: stale connection tracking after an edit

MikroTik’s NAT documentation is blunt: whenever NAT rules are changed or added, the connection tracking table should be cleared, or the rules may seem not to work until the old entry expires. A client that kept retrying during your edit holds an entry created under the old rules, and NAT only evaluates the first packet. Clear the relevant entries and retry:

/ip firewall connection remove [find dst-address~"192.168.88.10"]

If you run OPNsense elsewhere, the same failure classes exist under different names; the OPNsense port forward guide maps them to that platform’s reflection settings.

Things to test before you call it done

  • From outside (LTE, not Wi-Fi): nc -vz -w 5 <public-ip> 443 or nmap -Pn -p 443 <public-ip>. “open” is the only pass.
  • From inside, using the public name: curl -vk https://<hostname>/. This exercises hairpin or split DNS, whichever you chose.
  • dig +short <hostname> must equal the address from /ip address print where interface=pppoe-out1. A stale dynamic DNS record looks exactly like a broken forward.
  • /ip firewall nat print stats before and after each external test; the dst-nat counter must increment per attempt.
  • /ip firewall connection print where dst-address~":443" during a test: the entry must carry the dstnat flag and reach established.
  • For UDP services, test with the real client; nc -u proves nothing.

Leave the drop all from WAN not DSTNATed rule in place while testing. Disabling it to “see if the firewall is the problem” opens every LAN host to the internet. If you do it anyway, re-enable it before the next test.

Sources

  1. NAT - RouterOS Documentation
  2. Building Advanced Firewall - RouterOS Documentation
  3. Packet Flow in RouterOS - RouterOS Documentation
  4. Common Firewall Matchers and Actions - RouterOS Documentation
  5. Connection tracking - RouterOS Documentation
  6. RFC 4787: Network Address Translation (NAT) Behavioral Requirements for Unicast UDP
  7. RFC 6598: IANA-Reserved IPv4 Prefix for Shared Address Space
  8. dst-nat port forwarding not working - MikroTik community forum
  9. Hairpin NAT doesn't work - MikroTik community forum
#mikrotik #routeros #nat#port-forwarding #firewall #troubleshooting

Related