Working around NAT hairpin

Hello all,

A beginner here and I have been struggling for weeks now with this issue :slight_smile:

Some time ago I have successfully done port forwarding to access my server from external networks using a domain - lets call it testdomain.com

I kept the default network configuration 192.168.88.1/24 and the server had a fixed IP 192.168.88.200, everything worked fine from outside when accessing the domain, but from the LAN I would always be taken to the router login page.

I read dozens of threads, articles, youtube videos over enabling hairpin NAT, but none of the examples worked for me, I really have no idea why.

Then I read in a couple of threads here that an alternative is to simply put the server on a different subnet/network so I thought I’d try that.

What I did then is assign the Eth3 to have another network setup as 192.168.90.1/24 and put the server to have static IP 192.168.90.200 and connected it to that port.

Setup the server’s IPv4 to have 192.168.90.1 as gateway and 255.255.255.0 as subnet mask - the internet on the server works and it can see other computers and also other computers which have IP 192.168.88.XXX can see it.

If I try and access 192.168.90.200 from another PC browser, it correctly opens the website.

Also, I have altered the port forwarding rule in MT to point to the new IP and now when I am using external network to access testdomain.com the website is shown correctly.

Buuuut, I still cannot use testdomain.com from any computer on my internal network, for example a PC with IP 192.168.88.101, it still takes me to MT login page from any of those computers.
If I type 192.168.90.200 instead, it will correctly open the website.

Why is that so, did I miss the point completely, is the server not on separate network yet?

Here are some screenshots and the config:
Screenshot 2021-11-17 at 13.39.58.png

/ip pool
add name=default-dhcp ranges=192.168.88.10-192.168.88.99
/ip dhcp-server
add address-pool=default-dhcp disabled=no interface=bridge name=defconf
/interface bridge port
add bridge=bridge comment=defconf interface=ether2
add bridge=bridge comment=defconf interface=ether3
add bridge=bridge comment=defconf interface=ether4
add bridge=bridge comment=defconf interface=ether5
add bridge=bridge comment=defconf interface=ether6
add bridge=bridge comment=defconf interface=ether7
add bridge=bridge comment=defconf interface=ether8
add bridge=bridge comment=defconf interface=ether9
add bridge=bridge comment=defconf interface=ether10
add bridge=bridge comment=defconf interface=sfp1
add bridge=bridge comment=defconf interface=wlan1
/ip neighbor discovery-settings
set discover-interface-list=LAN
/interface list member
add comment=defconf interface=bridge list=LAN
add comment=defconf interface=ether1 list=WAN
/ip address
add address=192.168.88.1/24 comment=defconf interface=bridge network=192.168.88.0
add address=192.168.90.1/24 comment=Servers interface=ether3 network=192.168.90.0
/ip dhcp-client
add comment=defconf disabled=no interface=ether1
/ip dhcp-server network
add address=192.168.88.0/24 comment=defconf gateway=192.168.88.1
/ip dns
set allow-remote-requests=yes servers=8.8.8.8
/ip dns static
add address=192.168.88.1 comment=defconf name=router.lan
/ip firewall filter
add action=accept chain=input comment="defconf: accept established,related,untracked" connection-state=established,related,untracked
add action=drop chain=input comment="defconf: drop invalid" connection-state=invalid
add action=accept chain=input comment="defconf: accept ICMP" protocol=icmp
add action=accept chain=input comment="defconf: accept to local loopback (for CAPsMAN)" dst-address=127.0.0.1
add action=drop chain=input comment="defconf: drop all not coming from LAN" in-interface-list=!LAN
add action=accept chain=forward comment="defconf: accept in ipsec policy" ipsec-policy=in,ipsec
add action=accept chain=forward comment="defconf: accept out ipsec policy" ipsec-policy=out,ipsec
add action=fasttrack-connection chain=forward comment="defconf: fasttrack" connection-state=established,related
add action=accept chain=forward comment="defconf: accept established,related, untracked" connection-state=established,related,untracked
add action=drop chain=forward comment="defconf: drop invalid" connection-state=invalid
add action=drop chain=forward comment="defconf: drop all from WAN not DSTNATed" connection-nat-state=!dstnat connection-state=new in-interface-list=WAN
/ip firewall nat
add action=masquerade chain=srcnat comment="defconf: masquerade" ipsec-policy=out,none out-interface-list=WAN
add action=dst-nat chain=dstnat comment="port forward for server" dst-port=80 in-interface-list=WAN protocol=tcp to-addresses=192.168.90.200
add action=masquerade chain=srcnat comment="NAT hairpin" disabled=yes dst-address=!192.168.88.1 src-address=192.168.88.0/24
add action=dst-nat chain=dstnat comment="Hairpin NAT 2" disabled=yes dst-address=192.168.88.1 dst-address-type=local dst-port=80 protocol=tcp to-addresses=192.168.88.200 to-ports=80
/lcd interface pages
set 0 interfaces=sfp1,ether1,ether2,ether3,ether4,ether5,ether6,ether7,ether8,ether9,ether10

You have used the incorrect dst-nat for local access. You have used in-interface-list=wan and local traffic does not reach the wan interface. You options to fix it by adding a static dns entry pointing towards your server, but this only works if you are using a local dns.

If you have a static public IP address, using dst-address is the recommended method.

And you came close to using one of the methods for a dynamic IP address with one of your attempted hairpin nat. You just left out the “!”. (! = not)

add action=dst-nat chain=dstnat comment="Hairpin NAT 2" disabled=yes dst-address=192.168.88.1 dst-address-type=local dst-port=80 protocol=tcp to-addresses=192.168.88.200 to-ports=80

Should have been:

add action=dst-nat dst-address=!192.168.88.1 dst-address-type=local dst-port=80 protocol=tcp to-addresses=192.168.88.200 to-ports=80

Or you can put an dynamic dns in an address-list and use dst-address-list.

https://forum.mikrotik.com/viewtopic.php?t=179343

Unfortunately I do not have a static IP adress, it is dynamic, I am using my domain registrar’s dynamic DNS feature which works great, on previous router all I had to do was select “NAT loopback” and it worked without any further hassle :slight_smile:

So, you suggest I create this rule, and everything should work fine?

add action=dst-nat dst-address=!192.168.88.1 dst-address-type=local dst-port=80 protocol=tcp to-addresses=192.168.88.200 to-ports=80

What about the separate network I have created which transferred the server to 192.168.90.200, should I let it like that, or roll back to .88 or it is irrelevant?

I noticed this great guide, but I must have been doing something wrong :slight_smile:

That’s why I decided to try and follow this guideline:

There are two quick work-arounds to avoid the necessity to deal with Hairpin NAT and they include:
a. Move the LAN users OR Server to a different subnet, done!

Now I have no idea, have I done that correctly when I moved my server to 192.168.90.200 or I missed the whole thing completely?

Is there any pro/con of choosing either approach if I don’t really care which network the server is on as long as I can access it locally/externally from other computers?

What do you guys suggest I do? :slight_smile:

Hi there,
If its no issue to move the server to a different subnet then you are done!
Often that is not possible and then you have to look at the alternate options laid out in the article.
So yes,

Solution A: Move subnet to different LAN (or users)
/ip firewall nat
add action=masquerade chain=srcnat comment=“defconf: masquerade” ipsec-policy=out,none out-interface-list=WAN
add action=dst-nat chain=dstnat comment=“port forward for server” dst-port=80 in-interface-list=WAN protocol=tcp to-addresses=192.168.90.200

Should be all that is required! All users on the primary LAN .88, should be able to reach the server using the domain address.

Solution B: In all cases (other than the DNS method) you will need this as masquerade rules
/ip firewall nat
add action=masquerade chain=srcnat comment=“defconf: masquerade” ipsec-policy=out,none out-interface-list=WAN
add action=masquerade chain=srcnat dst-address=192.168.88.0/24 src-address=192.168.88.0/24

Since you have a dynamic WANIP, the basic DST nat rule wont suffice, and you have three ways to modify the rule…
a. as per the youtube video (using routers ip cloud and pulling that into a firewalldress - and using that as destination to replace in-interface-list=WAN)
b. basically the same concept using a script to pull the WANIP into a firewall address - and using that as destination to replace in-interface-list=WAN
c. writing a kludge rule that tells the router to use a local router interface that is not the available LAN interface WAN (so only local interface left is the WAN one)

Solution C:
Dont need extra source nat rule and can use standard DST NAT RULE, (but need to do DNS redirect )

Haha unfortunately I feel so dumb, I still haven’t worked it out :slight_smile:

Okay, just to cover the basics, about this:

If its no issue to move the server to a different subnet then you are done!
Move subnet to different LAN (or users)

So, my primary network is 192.168.88.x, and all the client computers are there.

The server is on another network at 192.168.90.x with IP 192.168.90.200

Did I do that correctly, does that mean that the server is on another subnet - did I make the prerequisite for proceeding further?

I assumed it to be, so I tried to follow through with solution A, so, my rules are now as follows (only 2 enabled):

/ip firewall nat
add action=masquerade chain=srcnat comment=“defconf: masquerade” ipsec-policy=out,none out-interface-list=WAN
add action=dst-nat chain=dstnat comment=“Port Forward for server v2” dst-port=80 in-interface-list=WAN protocol=tcp to-addresses=192.168.90.200
add action=dst-nat chain=dstnat comment=“port forward for server” disabled=yes dst-port=80 in-interface-list=WAN protocol=tcp to-addresses=192.168.90.200
add action=masquerade chain=srcnat comment=“NAT hairpin” disabled=yes dst-address=!192.168.88.1 src-address=192.168.88.0/24
add action=dst-nat chain=dstnat comment=“Hairpin NAT 2” disabled=yes dst-address=192.168.88.1 dst-address-type=local dst-port=80 protocol=tcp to-addresses=192.168.88.200 to-ports=80

Is that all there is to do, it should have been working now or still not?

Because it isn’t :slight_smile:

I have just tried with a PC which has an IP 192.168.88.201 and it was unforunately still not working, I was taken to the RB login again.

Any ideas what I might be missing? :slight_smile:

That is weird, it is confusing as you have kept rules that you dont need but noticed they are disabled.

Suggest post your entire config for review
/export hide-sensitive file=anynameyouwish

For connections WAN->server you need only first two NAT rules:

/ip firewall nat
add action=masquerade chain=srcnat comment="defconf: masquerade" ipsec-policy=out,none out-interface-list=WAN
add action=dst-nat chain=dstnat comment="Port Forward for server v2" dst-port=80 in-interface-list=WAN protocol=tcp to-addresses=192.168.90.200

Reason that the DST-NAT rules doesn’t work for LAN->server connections is use of in-interface-list=WAN. Basically you want to keep using WebFig from LAN. But LAN->server connection doesn’t enter router through WAN interface. With statical WAN IP the problem is easily solved: use dst-address=WAN.IP instead. With dynamic IP address it doesn’t work, some more clever stuff is needed. For example instead of second rule above use the following one:

add action=dst-nat chain=dstnat comment="Port Forward for server v3" dst-port=80 protocol=tcp dst-address-type=local dst-address=!192.168.88.1 to-addresses=192.168.90.200

The first property (dst-address-type) makes sure that this rule only affects packets targeting one of router’s own addresses (either WAN, LAN or server’s subnet), the second property (dst-address=!) excludes packets sent to LAN IP address of router. In case when there are multiple LAN subnets in game (and you want to offer WebFig access to router from all LANs), one would create address list, add all router’s LAN addresses to it and use property dst-address-list=!LANaddressList instead of single LAN address.

BTW, the problem solved by rule above is exactly the same when server is either in separate LAN or same LAN (in later case one needs third NAT rule - src-nat for LAN->LAN connections which is the gist of hair-pin NAT), it is only solving the problem of dynamic WAN address.

So MKX,
Having the server on a different subnet from the Users DOES NOT avoid hairpin nat??
Further the issue is the makeup of the normal dstnat rule and thus the real culprit is
“in-interface-list=WAN” or in-interface=WAN for a dynamic WANIP??

If so I have been working from a wrong assumption all this time. :frowning:

As I wrote another dozen times already, use the routerboard to resolve the internal DNS requests, (or set on your DNS server) that testdomain.com A record is 192.168.88.101

Outside all works because external DNS point testdomain.com to routerboard (natted port?),
internally all works because testdomain.com are resolved as 192.168.88.101,
and work all also legit SSL certificates.
No hairpin NAT or “different subnet” are involved.

Understood rextended and that is one of the options available and probably the best one :wink:
However, I was under the impression moving the server to a different subnet would solve it as well, no tricks required!!

1 or few servers/ports: Use internal DNS
Many servers/ports: Use Hairpin

When using Hairpin NAT traffic from local host to local server will go trough router instead of directly to the server.
Normally not a problem.

If you want use that method, why move and probably create more problems on devices than still point the server, on the right way, with correct IP?
Simple add another subnet just for this.

My apologies dabardabar, the guide on simply moving the server to a different subnet was not complete.
Instead of this the basic default rules.
/ip firewall nat
add action=masquerade chain=srcnat out-interface-list=WAN
add action=dst-nat chain=dstnat dst-port=80 protocol=tcp
in-interface-list=WAN to-addresses=192.168.90.200

Use the following
/ip firewall nat
add action=masquerade chain=srcnat out-interface-list=WAN
add action=dst-nat chain=dstnat dst-port=80 protocol=tcp
in-interface-list=portforwarding to-addresses=192.168.90.200

So basically the extra step includes adding another interface list entry and then defining the members.
Give it a go and see if it works!!

/interface list
add name=portforwarding
/interface list members
{already existing entries plus}
add interface=ether1 list=portforwarding
add interface=bridge list=portforwarding

It does. Hairpin-NAT is when clients are on the same subnet as server but they are not aware of it (because they think they’re talking to some internet host) - the issue is return traffic (server sees clients from same subnet and bypasses NAT entity). If server is on another subnet, then server sees clients are not in same subnet and uses its gateway (the DST-NAT device).


As I wrote before: rule property in-interface-list=WAN prevented the rule from performing DST-NAT for packets entering router from LAN.

I have not tested this, but I do not think this works.

You have a router with two VLAN.
VLAN 1:192.168.1.0/24 Client net
VLAN 2:192.168.8.0/24 Server net

Public IP 12.12.12.12
NAT www.myserver.home port 80/tcp 12.12.12.12->192.168.8.12

If a client on a VLAN 1 tries to reach the server, DNS will give IP 12.12.12.12 when you ask a public IP.
Will the router automatically redirect the data to the internal server? I do not think so.

Using Hairpin or a static internal DNS record for www.myserver.home to 192.168.8.12 will make you reach the server.

(not for @Jotne)
All this effort for not to use the easy DNS solution I suggested?

Let me try to explain it slowly.

Consider the following example:
WAN IP 12.12.12.12
client LAN subnet: 192.168.10.0/24
server LAN subnet: 192.168.20.0/24

One has DST-NAT set up (the following one should not be used as it interferes with normal LAN->WAN connectivity because it is too simplified):

/ip firewall nat
add chain=dstnat action=dst-nat protocol=tcp dst-port=80 to-addresses=192.168.20.0

Now the packet flow: client from internet → router → DST-NAT → server . Server sees connection from internet IP address and uses router for return traffic, router undoes DST-NAT and traffic flows.
client from client LAN → router → DST-NAT → server . Server sees connection from client LAN IP address and uses router for return traffic, router undoes DST-NAT and traffic flows.

And the above is straight DST-NAT, hairpin is not in the picture. It is only necessary to construct the DST-NAT rule which applies to all traffic, not only traffic ingressing through WAN interface (which was the case with OP’s initial configuration).

Now another scenario:

WAN IP 12.12.12.12
LAN subnet: 192.168.10.0/24 (both server and clients are in same subnet)

Using same (very greedy) DST-NAT rule the internet access to the NATed server works just like in the previous case. Packet flow for connections from LAN clients is different: client → router → DST-NAT → server . Server sees src-address from LAN (note that DST-NAT only changes dst-address and dst-port) and sends return traffic directly. Which causes two problems: 1) NAT router doesn’t see return traffic and hence doesn’t update connection tracking state for tjis connection and 2) client rejects return traffic because src-address of return traffic doesn’t correspond with any open connection. Both problems will block connection but problem #2 is the one blocking it earlier.

Here comes the hairpin NAT part deux: additional NAT rule in form

/ip firewall nat
add chaon=srcnat action=masquerade dst-address=192.168.10.0/24 src-address=192.168.10.0/24

This alters the packet flow: client → touter → DST-NAT → SRC-NAT → server . Server sees connection to originate from router itself, hence directs return traffic towards router, router undoes both NATs and traffic between client and server flows.

I hope that above explanation makes clear when hairpin NAT is necessary and when not.

@regextended: using split DNS certainly does have certain positive sides, one is simplified firewall and NAT configuration, server sees client’s real IP address instead of router’s address. But at the same time comes with a few drawbacks (some of them are result of DST-NAT for public access): one has to bother with setting up local DNS server (DNS service on ROS is hardly adequate), if using service from internet and LAN when on internet port used is not standard one has to remember to change service connection setup (it’s fairly easy with http/https, not so easy for other services) while taking care of actually used network access … so sometimes it is easier to simply add the LAN-LAN SRC-NAT and slightly change the DST-NAT rule to avoid the split-DBS problems.

At the end of the day it’s up to network admin to choose which solution to implement. I always go with split-DNS solution recomended by @regextended

DNS override has exactly one advantage - traffic doesn’t have to unnecessarily go to router and back. Everything else speaks for hairpin NAT, see e.g.:

Why hairpin NAT is the best thing in the world (only slightly biased comparison with local DNS override :smiley:)

It’s almost boring, fully transparent, set it & forget it. The only (small) challenge with hairpin NAT is finding out how to create proper dstnat rules for your setup, that’s what people usually stuggle with. And even that is usualy simple to solve, only with NAT 1:1 it can be a little annoying.

Sob where you have been ole chum, I almost fainted when I saw you had posted!!
Is the real Sob? or some sick imposter??
You have made my day!