Hairpin NAT doesn't work

Hi,
I’m trying to set up Hairpin NAT on my MikroTik router, but nothing works. I’ve tried many guides, but none of them worked.

My Hairpin NAT and port forwarding rules:

/ip firewall nat
add action=masquerade chain=srcnat comment=DUPA dst-address=192.168.1.100 out-interface=\
    bridge_lan protocol=tcp src-address=192.168.1.0/24
/ip firewall nat
add action=dst-nat chain=dstnat comment="matrix 8448" dst-address-list=WAN dst-port=8448 \
    in-interface-list=WAN port="" protocol=tcp to-addresses=192.168.1.100 to-ports=8448

When I enter my public IP address in browser, webfig opens.

My device: hAP ax2 on RouterOS 7.17

/export file=anynameyouwish (minus router serial device, any public WANIP information, keys).

After you post the complete config, happy to comment.

# 2025-06-07 14:22:10 by RouterOS 7.17
# software id = XAII-WW51
#
# model = C52iG-5HaxD2HaxD
# serial number = XXXX
/interface bridge
add name=bridge_guest
add name=bridge_lan
/interface ethernet
set [ find default-name=ether2 ] comment=AP
set [ find default-name=ether5 ] comment=switch
/interface wireguard
add listen-port=1234 mtu=1420 name=wireguard
/interface vlan
add interface=ether1 name=vlan35 vlan-id=35
/interface pppoe-client
add add-default-route=yes disabled=no interface=vlan35 name=orange-ppp user=xxxx@neostrada.pl
add add-default-route=yes disabled=no interface=vlan35 name=orange-ppp-v6 use-peer-dns=yes user=xxxx@neostrada.pl/ipv6
/interface list
add name=WAN
add name=LAN
/interface wifi configuration
add country=Panama disabled=no name=dupa security.authentication-types=wpa2-psk .wps=disable ssid=XXXX
add country=Panama disabled=no name=hotspot security.authentication-types=wpa-psk,wpa2-psk .ft=yes ssid=XXXX
/interface wifi
set [ find default-name=wifi1 ] configuration=hotspot configuration.mode=ap disabled=no security.authentication-types=wpa-psk,wpa2-psk .wps=disable
set [ find default-name=wifi2 ] configuration=hotspot configuration.antenna-gain=0 .mode=ap disabled=no security.authentication-types=wpa-psk,wpa2-psk .wps=disable
add configuration=dupa configuration.mode=ap mac-address=XXXX master-interface=wifi1 name=wifi3
add configuration.mode=ap .ssid=test mac-address=XXXX master-interface=wifi2 name=wifi4
/ip pool
add name=dhcp_pool0 ranges=192.168.1.100-192.168.1.254
add name=dhcp_pool1 ranges=192.168.21.2-192.168.21.254
/ip dhcp-server
add address-pool=dhcp_pool0 interface=bridge_lan lease-time=1d name=dhcp1
add address-pool=dhcp_pool1 interface=bridge_guest name=dhcp2
/interface bridge port
add bridge=bridge_lan interface=ether2
add bridge=bridge_lan interface=ether3
add bridge=bridge_lan interface=ether4
add bridge=bridge_lan interface=ether5
add bridge=bridge_guest interface=wifi2
add bridge=bridge_guest interface=wifi1
/interface list member
add interface=orange-ppp list=WAN
add interface=bridge_lan list=LAN
add interface=*C list=LAN
add interface=bridge_guest list=LAN
add interface=wireguard list=LAN
/ip address
add address=192.168.1.1/24 interface=bridge_lan network=192.168.1.0
add address=192.168.100.2/24 interface=ether1 network=192.168.100.0
add address=192.168.11.1/24 interface=wireguard network=192.168.11.0
add address=192.168.21.1/24 interface=bridge_guest network=192.168.21.0
/ip cloud
set ddns-enabled=yes ddns-update-interval=2m
/ip dhcp-server lease
add address=192.168.1.200 client-id=XXXXmac-address=XXXX server=dhcp1
add address=192.168.1.100 client-id=XXXX mac-address=XXXX server=dhcp1
/ip dhcp-server network
add address=192.168.1.0/24 dns-server=192.168.1.1 domain=lan gateway=192.168.1.1
add address=192.168.21.0/24 dns-server=192.168.21.1 gateway=192.168.21.1
/ip dns
set allow-remote-requests=yes servers=8.8.8.8,8.8.4.4
/ip firewall address-list
add address=192.168.1.0/24 list=LAN
add address=homelab.mily.ovh list=WAN
/ip firewall filter
add action=accept chain=input comment=Wireguard dst-port=13231 protocol=udp
add action=accept chain=input connection-state=established,related,untracked
add action=drop chain=input connection-state=invalid
add action=accept chain=input protocol=icmp
add action=drop chain=input in-interface-list=!LAN
add action=fasttrack-connection chain=forward connection-state=established,related hw-offload=yes
add action=accept chain=forward connection-state=established,related,untracked
add action=drop chain=forward connection-state=invalid
add action=drop chain=forward connection-nat-state=!dstnat connection-state=new in-interface-list=WAN
add action=drop chain=forward comment="block lan from hotspot" dst-address=192.168.1.0/24 src-address=192.168.21.0/24
/ip firewall nat
add action=masquerade chain=srcnat comment=DUPA dst-address=192.168.1.100 out-interface=bridge_lan protocol=tcp src-address=192.168.1.0/24
add action=masquerade chain=srcnat comment=NAT out-interface-list=WAN
add action=dst-nat chain=dstnat comment="matrix 8448" dst-address-list=WAN dst-port=8448 in-interface-list=WAN port="" protocol=tcp to-addresses=192.168.1.100 to-ports=8448
/ip route
add dst-address=192.168.11.0/24 gateway=wireguard
add disabled=no dst-address=192.168.1.0/24 gateway=bridge_lan routing-table=main suppress-hw-offload=no
/ip service
set telnet disabled=yes
set ftp address=192.168.1.0/24,192.168.11.0/24
set www address=192.168.1.0/24,192.168.11.0/24
set ssh address=192.168.1.0/24,192.168.11.0/24
set api disabled=yes
set winbox address=192.168.1.0/24,192.168.11.0/24
set api-ssl disabled=yes
/ipv6 address
add from-pool=orange interface=bridge_lan
/ipv6 dhcp-client
add interface=orange-ppp-v6 pool-name=orange pool-prefix-length=56 request=prefix use-peer-dns=no
/ipv6 dhcp-server
add address-pool=orange dhcp-option=custom-dns interface=bridge_lan name=server1 prefix-pool=orange
/ipv6 firewall filter
add action=accept chain=input comment="Allow established/related input" connection-state=established,related disabled=yes
add action=accept chain=forward comment="Allow established/related forward" connection-state=established,related disabled=yes
add action=accept chain=forward comment="Allow WAN access to host on ports 22, 80, 443" disabled=yes dst-address=xxxx dst-port=22,80,443 protocol=tcp
add action=accept chain=forward comment="Allow ICMPv6 ping to host" disabled=yes dst-address=XXXX icmp-options=128:0 protocol=icmpv6
add action=accept chain=forward comment="Allow intra-LAN IPv6 traffic" disabled=yes dst-address=XXXX src-address=XXXX
add action=drop chain=input comment="Drop all other input" disabled=yes
add action=drop chain=forward comment="Drop all other forward" disabled=yes
/system clock
set time-zone-name=Europe/Warsaw
/system identity
set name=router-dom
/system note
set show-at-login=no
  1. I am not familiar with IPV6 so cannot comment on two PPPOE settings.

  2. This shows an error in an interface selection
    add interface**=C* list=LAN

  3. This address should be removed…
    add address=192.168.100.2/24 interface=ether1 network=192.168.100.0

Your WAN connection coming on vlan35 from the ISP over ether1 is handled/terminated by the PPPOE connections!!

  1. To setup firewall rules for port forwarding from exterior and interior modify your forward chain rules, after the good default ones there
    from:
    add action=drop chain=forward connection-nat-state=!dstnat connection-state=new in-interface-list=WAN
    add action=drop chain=forward comment=“block lan from hotspot” dst-address=192.168.1.0/24 src-address=192.168.21.0/24

TO:
add action=accept chain=forward comment=“internet traffic” in-interface-list=LAN out-interface-list=WAN
add action=accept chain=forward comment=“wg to LAN” in-interface=wireguard out-interface-list=LAN
add action=accept chain=forward comment=“port forwarding” connection-nat-state=dstnat
add action=drop chain=forward comment=“drop all else”

The last rule should drop any L3 traffic between bridges, you can test.
However if you want LAN to reach hotspot you would have to add AN ALLOW rule for that traffic before the drop all else rule at the end.

  1. Hairpin NAT rule" - modify, simply a bit:
    add action=masquerade chain=srcnat comment=DUPA dst-address=192.168.1.0/24 src-address=192.168.1.0/24
    add action=masquerade chain=srcnat comment=NAT out-interface-list=WAN

  2. Terrible nomenclature, keep firewall address list name separate from interface names already in use aka WAN
    /ip firewall address-list
    add address=192.168.1.0/24 list=LAN <---- I would change this too, as it conflicts with an existing interface name.
    add address=homelab.mily.ovh list=myWAN

THEN apply to dstnat rule but REMOVE in-interface-list=WAN
/ip firewall nat
add action=dst-nat chain=dstnat comment=“matrix 8448” dst-address-list=myWAN dst-port=8448
protocol=tcp to-addresses=192.168.1.100

Note: if To port same as dst-port, its not required to add.

Ok, thank you. I will try your solution.
192.168.100.2 on ether1 is for ONT management

Hairpin NAT still doesn’t work

Here’s my config, adjust as needed:

/interface list
add name=wan

/interface list member
add interface=ether1_wan list=wan
add interface=6to4_waw1 list=wan
add interface=lte1 list=wan

/ip firewall filter
add action=accept chain=forward comment=hairpin/dst-nat connection-nat-state=dstnat in-interface-list=wan src-address-list=!rfc-1918
add action=accept chain=forward connection-mark=hairpin connection-nat-state=dstnat

/ip firewall mangle
add action=mark-connection chain=prerouting comment="mark packets destined to wan from internal as hairpin" dst-address-list=wan in-interface-list=!wan new-connection-mark=hairpin src-address-type=!local

/ip firewall nat
add action=masquerade chain=srcnat comment="masquarade srcnat" out-interface-list=wan
add action=netmap chain=srcnat comment="masquarade hairpin" connection-mark=hairpin to-addresses=100.64.0.0/10
add action=dst-nat chain=dstnat comment=www dst-address-list=wan dst-port=80,443 protocol=tcp to-addresses=10.11.0.123

/ip firewall address-list
add address=192.168.0.0/16 list=rfc-1918
add address=172.16.0.0/12 list=rfc-1918
add address=10.0.0.0/8 list=rfc-1918
add address=0.0.0.0/8 list=rfc-1918
add address=224.0.0.0/3 list=rfc-1918
add address=127.0.0.0/8 list=rfc-1918
add address=204.152.64.0/23 list=rfc-1918
add address=169.254.0.0/16 list=rfc-1918
add address=255.255.255.255 list=rfc-1918
add address=**.**.**.*** comment="dynamic ether1_wan" list=wan
add address=*.***.***.*** comment="static lte1" list=wan

If your IP is dynamically assigned by DHCP, you can borrow this (slightly modified to remove manual route mangling) script to set address-list entry:

:local name [/interface/get $interface name]

/ip/firewall/address-list/remove [find list=wan comment="dynamic $name"]

:if ($bound = 1) do {
  :if ([:len ($"lease-options"->"6")]) do {
    /ip/firewall/address-list/add list=wan address=$"lease-address" comment="dynamic $name"
  }
}

When using it, I also recommend creating scheduler entry as such:

/system scheduler
add comment="runs one time on boot; cleans stale address-list" name=onboot on-event="# clean stale addresses\r\n/ip/firewall/address-list/remove [find list=wan comment~\"^dynamic .+\"]" policy=read,write,policy,test start-time=startup

OP, post your config with the changes suggested by anav.

As for IPv6, I’m no expert here but the firewall seems a bit breezy (no active drop rules)

Eider, why do you post a config that is nothing but a security phuckup…
Ahh okay, that makes more sense…

Clearly it’s a snippet, not entire configuration? I’ve only included what’s needed for hairpin using netmap. That’s the entire point of textual configuration, isn’t it? We share snippets, and people learn from them and adapt them to their own config. No different than sharing snippets for Junos or IOS. The person already has their own config, I’m not gonna force my own filter rules on them (not that they would even fit considering how complex my entire configuration is in forward chains).

I’m only going to comment on the “hairpin nat” part of things, because that was the question - however I fully agree that securing your network and router are important, and consideration should be given to that.

Hairpin NAT is used to access “port forwarded” services from not only outside the network, but from the inside as well. Both rules have to be adequately configured for things to work.

The “port forwarding rule” has to filter by the dst ip addr and not the input interface, because when the forwarded service is accessed from the inside, then - well - the connection is coming from inside the network and not outside.

There are two options: One in to maintain an address list containing the external address of the router. I use this approach on my own routers, but it usually means scripting.

The other: use dst-address-type=local, which means that the router is addressed on any of its own addresses. (This may be slightly counter-intuitive: type local doesn’t mean “local” in the sense of “on your LAN”, but means the addresses that are “local to the router itself”, that is: the router’s own addresses.)

So the rule:

/ip/firewall/nat/add chain=dstnat action=dst-nat dst-address-type=local protocol=tcp dst-port=8080 to-addresses=192.168.88.10 to-ports=80

(This rule maps port 8080 to an internal server 192.168.88.10, and sends the connection to port 80.)

The other rule is the actual hairpin rule. I think that hairpin nat should generally be regarded as a not elegant, but sometimes very useful hack. Therefore I usually create hairpin rules to only affect traffic that absolutely needs it (and leaves other connections be). This is therefore my haripin rule corresponding to the above rule:

/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=80

Note that when the hairpin (srcnat) rule is executed, the port forwarding (dstnat) has already been done, and the packet now matches the rule in its translated state with regard to its dst address and dst port, as shown in my example. It’s important to include the src-address criterion in the hairpin rule, otherwise all access (even from external sources) will appear as coming from the router’s local address.

Happy hacking!