Last updated: August 14, 2013 at 20:30 pm

What is Hairpin NAT and when/why would you want to use it?  When you want to reach a device from the same DynDNS address whether you’re on the LAN or coming in remotely.

How basic Port Forwarding would work

Imaginary IP camera at

From inside the LAN:

Go to
You get the IP camera interface.

From outside the LAN:

If you have port 80 forwarded to, you could go to (your DynDNS address) and it would take you to the same IP camera interface.

How Hairpin NAT would work

From inside the LAN:

Go to
You get the IP cam interface.

From outside the LAN:

Go to
You get the IP cam interface.

More advanced example

Let’s say you have four IP cameras, and you want to have your customer be able to pull up each one from a bookmark on their laptop/smartphone. Since the laptop/smartphone is mobile, they will sometimes be accessing them remotely. You could set up a “Front Porch Camera – Local” and “Front Porch Camera – Remote”, but that’s Kustom with a capital K.

The cameras can be accessed locally as:

  • Front Cam – – port 80
  • Back Cam – – port 80
  • Pool Cam – – port 80
  • Driveway Cam – – port 80

Your bookmarks for the customer could be as simple as this, and it will work whether they’re at home on their wifi or at a hotel across the coutnry:

  • Front Cam –
  • Back Cam –
  • Pool Cam –
  • Driveway Cam –

Setting up Hairpin NAT

Setting this up is a lot like setting up port forwarding (NAT), but there are a few more steps. You will need to do the following:

  1. Set up a port forwarding (dst-nat) rule for each camera and give it a comment with “hairpin” in it (you’ll see why…)
  2. Set up a masquerade rule for your LAN. (This is the only ‘confusing’ thing about setting this up. If you don’t set up this masquerade rule, the requests will appear to be coming from a LAN and the replies will be coming from the WAN address. Either way, it won’t work, and you’ll just be sitting there wondering why it won’t connect.)
  3. Set up a script to update your “hairpin” rules when your WAN IP changes.
    1. This works in conjunction with the DynDNS script. You will need to edit it so that it runs your “hairpin_update” script when the WAN changes. I will show you how to do this, it’s really simple.

Step 1 – setting up the port forwarding rules

Go to IP / Firewall / NAT and set up four rules like this, adjusting the IP’s for the four cameras and the Dst Port for the desired URL. This is how the Front Cam would be set up. Use in the Dst. Address for all of them. Our script in step 3 will update this to your current WAN IP.



When you’re done you should have four new NAT rules for Dst Port 8090, 8091, 8092, and 8093 and the respective IP addresses of, 181, 182, and 183.  Make sure on the Action tab you use port 80 for all of them since you are still going to be accessing the camera through its webserver on port 80.

Now on each of these new rules, give them a comment with the word “hairpin” in it. It just has to have “hairpin” anywhere in it. It could be “front camera hairpin” and it will work. This will get used in step 3…

Step 2 – setting up the masquerade rule (thanks Springs for making this 10x simpler)

First step of this – set up an Address List for your entire LAN subnet.  Go to IP / Firewall / Address Lists.  Click the + to add a new List, then in the Address field enter whatever is appropriate for your subnet. For example, my LAN subnet is, my router IP is, etc, so I entered and named it LocalNet.


Now that this is set up, you can make a single masquerade rule that will apply to your entire LAN instead of having to set up a rule for each individual device’s IP. For the Out. Interface you have to pick the interface that your LAN is running on.  For example, on an RB750GL, by default this is ether2. On an RB2011 the interface you want to use is bridge-local Do that next:

hairpin_rule1_updated-2 hairpin_rule2 hairpin_rule3

This is the confusing bit. You have to set up a masquerade rule that says “any traffic coming FROM the network (your LAN in this example) and going TO, AND is going OUT the ether2 port, make that traffic think it is coming from the network.

Step 3 – setting up a script to update your NAT rules

Go to System / Script and set up a new script. Name it hairpin_update. (It’s easier to play with your scripts in the Terminal if you don’t put any spaces in the names)

This is my entire script, I will explain each line below:

:global hostname
:global resolvedIP [:resolve $hostname]
/ip firewall nat set [find comment~"hairpin"] dst-address=$resolvedIP

The first line pulls in a variable that I already have in my router. It’s from my DynDNS script. Since it’s a “global” variable, it’s saved in the router’s memory and any other script can refer to it. The variable is equal to my DynDNS address, or in the case of this example, it would be equal to “”.
Adding the line “:global hostname” it simply pulls that variable into the memory, so when we refer to it on line 2, it knows what the value of ‘hostname’ is.

The second line creates a global variable called resolvedIP, then sets its value to the IP address of by asking the DNS server where that DNS name points to. This could just as easily be a Local variable (:local), which would only be available inside this script, but I tend to make a lot of variables Global so I can play with them and refer to them from other scripts. If you go to System / Scripts and click on the Environments tab, you can see every Global variable saved on your router. It’s great for troubleshooting.

Now, the third line is where the magic happens. It says “In any NAT rule that has hairpin in the comment, update the dst-address to the IP address resolved from”

Open up your Firewall window so that you can see it with the Scripts window also open. You should see all those addresses in your NAT rules. If you did everything correctly, if you highlight your hairpin_updater script and click “Run Script”, in about 1 second you should see all your dstnat rules update to your WAN address. Pretty cool!

Next step – when do we want the hairpin_updater to run? To me it makes the most sense to add it to the DynDNS script, since it’s already looking for WAN address changes. Might as well update all those rules at the same time as it updates, right? I would also have it run at startup just to be safe (System / Scheduler, add new, run on startup, in the box in the middle just type hairpin_updater and nothing else. Now it will run whenever the router starts up.)

To do this, you have to go into your DynDNS updater script and add this line inside the part that has already checked for a change of the WAN IP and is sending an update to DynDNS. Since you’re going to be adding it ‘inside’ a conditional statement, you have to make sure you add the correct number of spaces at the beginning of the line. Just make sure it lines up with everything else. In this example it is two spaces.

This is the last part of the DynDNS script. The line in red is what you need to add to make your hairpin_updater script run.

:if (($currentIP != $resolvedIP) || ($dyndnsForce = true)) do={
  :set dyndnsForce false
  :set previousIP $currentIP
  /tool fetch user=$username password=$password mode=http address="" \
  src-path="/nic/update?hostname=$hostname&myip=$currentIP" dst-path="/dyndns.txt"
  :local result [/file get dyndns.txt contents]
  :log info ("UpdateDynDNS: Dyndns update needed")
  :log info ("Thanks Springs! Update Result: " . $result)
  :put ("Dyndns Update Result: " . $result)
  /system script run hairpin_updater
} else= {
  :log info ("UpdateDynDNS: No dyndns update needed")

Time to test it out

On your computer, on the LAN, go to You should see the Front Cam. Now go to You should see the Front Cam again. Now get on your phone and OFF of their wifi and try coming in from the internet, again at You should see the Front Cam yet again, just as if you were on their network.

There you go. Your four bookmarks can now be used in the house or from across the internet.

Front Cam –
Back Cam –
Pool Cam –
Driveway Cam –

  • Springs

    Uggg… JIM!!!!


    You don’t need to masquerade each rule.

    This is where address lists are also useful.

    Lets start with the first part…
    /ip firewall address-list
    (Some of you guys who already use my firewall should know this but here we go.)
    Enter this
    /ip firewall address-list
    add address= disabled=no list=RLCNet

    What this will do is make a GROUP that rules can be applied to.
    So it is spelling out: anything with IP address is part of the RLCNet address list or group.

    How is this useful??? Look at the next line.

    /ip firewall nat
    add action=masquerade chain=srcnat disabled=no dst-address-list=RLCNet \
    out-interface=ether2 src-address-list=RLCNet

    What that states: should something from the LAN ask for something on LAN DESPITE THAT I ASKED FOR IT FROM THE WAN. Make it appear to come from the WAN.

    Hence… only one masquerade rule to handle all your internal redirects.

    Put that under your DEFAULT RULE that looks like…
    /ip firewall nat
    add action=masquerade chain=srcnat comment=”default configuration” disabled=\
    no out-interface=ether1-WAN src-address-list=RLCNnet

    The router will only maqurade traffic from your LAN… because of the address list.
    The router will maqurade traffic that is on the LAN but EXPECTED from the WAN.

  • Ryan

    Chris, I think it was you who posted this at IP. However the way outlined above is much more clean, and you don’t need to keep the rules on the correct lines as it shows below. Did you discover a new way to do it since posting this? Or was it not you who posted it?

    First Make an Address List
    /ip firewall address-list
    add address= disabled=no list=RLCLocalNet

    This lets you apply rules to lots of addresses without typing in each IP.

    Next Masquerade your own network back on interface 2 (Where most of you have your LANs.)
    /ip firewall nat
    add action=masquerade chain=srcnat comment=”Masquerade Local BACK IN” disabled=no out-interface=ether2-master-local src-address-list=\

    Now using the DNS updater from this thread.
    Go to the bottom of the script and add this line.

    /ip firewall nat set 2 dst-address=”$previousIP”

    This will plug the WAN IP ADDRESS into NAT Rule 2
    Whats that… you have 30 Nat Rules…

    /ip firewall nat set 2,3,4,5,6,7,8,9,10,11 dst-address=”$previousIP”
    This line will plug it into rules 2-11

    Last Step…
    Those dst-nat rules in NAT.

    What all this will break down to in Mikrotik world.

    My Home DYNDNS address is:
    If a outside device needs to reach a webcam at
    When the request hits the router from the outside world… or the local network
    The router sees you want to talk to the device on dt-nat rule that forwards too 8081
    The router then makes that forward.
    The Masqurade rule allows the return information to APPEAR to come from the outside IP.

  • Springs

    I do improve as I go along.

    The processing the firewall rules in the dyndns updater was something I came up with a few months back. I posted it at the forum. Then Jim called one night and I was working on a router with him. I told him about it. Then he wrote an article.

    He never warns me when he does this… so the first post is usually me yelling… YOU FORGOT TO HOOK UP THE DOLL!!!

    But Jim and I have a running joke about working together. When I think its JIM PROOF, then its ready.

  • Springs


    Don’t use resolvedIP. At the point in the script where it occurs… IT GETS THE ADDRESS THAT DYNDNS THINKS IS YOUR IP. You want to use the one in previousIP.

    $previousIP is set when acutual != resolved.

    Granted… it does not fill in the blanks when the unit first starts. But it also does not try to update with an IP that is about to be changed.

    example in plain english… (or Springs Speak)
    DYNDNS updater is running.
    Set variables
    get some info
    Now… query dns server to what it has the DNS record as. (resolvedIP)
    Now… Get the actual WAN IP. Parse it. result. (currentIP)
    Then the If / Then
    If resolvedIP = currentIP Print info “NO UPDATE NEEDED”
    If resolvedIP ! = currentIP Then
    Set previous IP to currentIP
    Update DYNDNS with result.
    Result = Good Log Info “Thanks Springs”

    See the glitch?
    resolved IP is what we are checking to see if its out of date. No point in setting your rules to an out of date IP.


  • Dave

    Thanks for the info! I knew this was possible with Mikrotik and have needed this for a while.

  • Springs

    Forgot to update this…

    If you are behind another NAT device like a FiOS router. You will need to enter each rule 2 times.

    Once with the dst-address updating comment.
    Once with the interface of your WAN.

    This because a packet coming from the WAN does not have a dst-address of the WAN interface any longer. It is Nat’d when it comes back from the other router. So the rule that matches dst-address needs to be duplicated for EXTERNAL ACCESS.

  • Jason

    I copied the two scripts exactly and the DNS works, but can’t seem to get second one to work, it won’t update the firewall rule with WAN ip,

    • admin

      The script inside your dyndns updater doesn’t work?

    • Rich

      I have the same issue. The dyndns script does not run the hairpin_updater

      • admin

        Well, there’s no trick to that part of it. That line should have four spaces, then /system script run [name of your hairpin updater script].

        Try running that line from a Terminal window and see if it works.

  • Pingback: [Quick Steps] – Hairpin NAT | Networking For Integrators()

  • Matthew M

    This is fantastic!

    I’ve been trying to set up hairpin NAT for some time now, and it seems that everything I tried either didn’t work or had some negative affect.

    This, however, has worked without any issues. Previously, I had relied entirely on in-interface rather than dst-address since I have a dynamic WAN address and every other guide I found assumed a static WAN address.

    The only additional thing I had to do was add my domain to an address list (add domain to list’s comment and the IP address is updated by another script) that prevents certain hosts going through a VPN that some clients are using.

    Thank you very much!

    • Matthew M

      Apologies for another post, however just to point out, the address list for your LAN is not required. You can put your CIDR in “src/dst address” of the masquerade rule.

  • Andy Ng


    :resolve $hostname in terminal
    failure: bad name

    can’t seem to get this working.

    Andy Ng

    • admin

      In Winbox, go to System/Scripts then click on the Environment tab. Is there a “hostname” variable with what you think it is?

  • Andy Ng


    Found the problem, in dyndns script, the variable hostname was declare as local. Changing this to global solve the issue. Works wonderfully.

    Andy Ng

  • jack torres

    Awesome! finally a comprehensive explanation about hairpin NAT!!

  • John Deaton

    I went to the page link for the DynDNS script but page is corrupt. Can’t click link to download. Have tried multiple browsers. Can I get a direct download link?

    • admin

      It was a bad cache setting on my end. For some reason it was only affecting that page, but should be good to go now (and for a while now)

  • rich

    I cannot get this to work – I have tried it a dozen times but no traffic ever passes through the masquerade rule.

    the script is running
    The ip is updating

    it doesn’t look all that hard – what could I be missing?

    • admin

      Are you doing double-NAT? (e.g. is the Mikrotik plugged into another router?)

  • rich

    I reset the router completely and re-did everything. It works now.

    The only thing I still cannot get to work on multiple routers is the dyndns script running the hairpin updater. I have to use a separate scheduler to run it.

    I placed it exactly where you state to, I also tried it at the end of the script but the dyndns script never runs the hairpin updater script.

    • admin

      If you put it at the very end of the script and it doesn’t run, there must be some kind of syntax error (I would think). You can go in Terminal and it’ll give you some clues as to if there’s a syntax error in the script somewhere. Go to /system scripts and do “print brief”. It’ll give you a list of all your scripts with a number next to it. Let’s say that it’s #3. Type “print from=3″ and it’ll print out the whole script (it’s long, so it won’t fit on one screen and you can scroll up/down, quit, etc).

      If there’s a syntax error in the script, the character where the error is should be “highlighted” and show you what part the system doesn’t understand.

      You can also try running the script from Terminal and it might output an error to give you some ideas as well.

      Also, you can add some “debugging” stuff into the script to track where it’s failing. Like:

      :put message=”about to run hairpin_updater”
      /system script run hairpin_updater
      :put message=”hairpin_updater should have just run”

      Then run the script from Terminal