Hairpin NAT Problems

Hi,

Sorry for another hairpin NAT post. I have managed to get to the point of reaching the subdomain then getting a 'Secure Connection Failed' error. I have tried a few different solutions and this is as close as I've managed to get.

I am on mobile broadband with a public IP and have added my subdomains to an address list and the router is updating the IP address when it changes so that is ok. I seem to be struggling with the final steps so wondered if anyone can point me in the right direction please.

My config:

2026-10-07 10:13:54 by RouterOS 7.24.5

/interface ethernet
set [ find default-name=ether1 ] loop-protect=on
/interface lte
set [ find default-name=lte1 ] allow-roaming=no band=1,3,5,7,8,20
network-mode=lte,5g nr-band=28
/interface vlan
add comment=CCTV disabled=yes interface=ether1 name=CCTV vlan-id=20
add comment="Geust WiFi" disabled=yes interface=ether1 name="Guest WiFi"
vlan-id=50
add comment="Home Automation VLAN" disabled=yes interface=ether1 name=
HomeAutomation vlan-id=30
add comment="VLAN 10 Management" disabled=yes interface=ether1 name="VLAN 10"
vlan-id=10
add comment="WiFi VLAN" disabled=yes interface=ether1 name=WiFi vlan-id=40
/interface list
add comment=defconf name=WAN
add comment=defconf name=LAN
/interface lte apn
set [ find default=yes ] apn=3internet authentication=chap comment=
"Main Mobile Internet" ip-type=ipv4 name=3Internet use-network-apn=no
use-peer-dns=no
/ip pool
add name=dhcp ranges=10.2.1.100-10.2.1.200
/ip dhcp-server
add address-pool=dhcp interface=ether1 name=defconf
/queue type
add fq-codel-ecn=no kind=fq-codel name=fq-codel-ethernet-default
/queue interface
set ether1 queue=fq-codel-ethernet-default
/ip neighbor discovery-settings
set discover-interface-list=LAN
/ipv6 settings
set disable-ipv6=yes
/interface detect-internet
set detect-interface-list=WAN
/interface list member
add comment=defconf interface=ether1 list=LAN
add comment=defconf interface=lte1 list=WAN
/ip address
add address=10.2.1.1/24 comment=defconf interface=ether1 network=10.2.1.0
/ip dhcp-server network
add address=10.2.1.0/24 comment=defconf dns-server=10.2.1.8,1.1.1.1,9.9.9.9
gateway=10.2.1.1 netmask=24
/ip dns
set allow-remote-requests=yes servers=10.2.1.8,1.1.1.1,9.9.9.9
/ip dns static
add address=10.2.1.1 comment=defconf name=router.lan type=A
/ip firewall address-list
add address=cctv.tsilimawr.co.uk comment=CCTV list="WAN IP"
add address=cloud.tsilimawr.co.uk comment=Cloud list="WAN IP"
add address=axe.tsilimawr.co.uk comment=Matrix list="WAN IP"
add address=axechat.tsilimawr.co.uk comment="Matrix Web" list="WAN IP"
add address=axemas.tsilimawr.co.uk comment="Matrix AS" list="WAN IP"
add address=imm.tsilimawr.co.uk comment=Immich list="WAN IP"
add address=10.2.1.0/24 comment=MainLAN list=LAN_IP
/ip firewall filter
add action=drop chain=forward comment=MalwareFilteringOutbound
connection-state=established,related,new connection-type=""
dst-address-list=Malware-List out-interface=ether1
add action=drop chain=forward comment=MalwareFilteringInbound
connection-state=established,related,new in-interface=lte1
src-address-list=Malware-List
add action=drop chain=forward comment="Drop Block List Traffic Out"
connection-state=established,related,new,untracked dst-address-list=
"Block List"
add action=drop chain=input comment="Drop Block List IP Ranges"
connection-state=invalid,established,related,new,untracked
src-address-list="Block List"
add action=accept chain=forward comment="Internet Traffic" in-interface-list=
LAN out-interface-list=WAN
add action=accept chain=forward comment="Port Forwarding"
connection-nat-state=dstnat
add action=accept chain=input comment="Accept ICMP from LAN"
in-interface-list=LAN protocol=icmp
add action=drop chain=input comment="Drop ICMP from WAN" in-interface-list=
WAN protocol=icmp
add action=drop chain=input comment="Drop DNS from WAN" dst-port=53
in-interface-list=WAN protocol=tcp
add action=drop chain=input comment="Drop DNS from WAN" dst-port=53
in-interface-list=WAN protocol=udp
add action=accept chain=forward comment="https to reverse proxy" disabled=yes
dst-port=443 in-interface-list=WAN log=yes protocol=tcp src-address-list=
""
add action=accept chain=input comment="Accept Related" connection-state=
related
add action=accept chain=forward comment=
"defconf: accept established,related, untracked" connection-state=
established,related,untracked
add action=accept chain=input comment="Accept Established" connection-state=
established
add action=fasttrack-connection chain=forward comment="defconf: fasttrack"
connection-state=established,related
add action=drop chain=forward comment="defconf: drop invalid"
connection-state=invalid
add action=drop chain=forward comment="Drop All Not Matching Above"
add action=drop chain=forward comment=
"defconf: drop all from WAN not DSTNATed" connection-nat-state=!dstnat
disabled=yes in-interface-list=WAN
add action=drop chain=input in-interface-list=!LAN
/ip firewall mangle
add action=mark-connection chain=prerouting comment=
"Mark connections for hairpin NAT" dst-address-list="WAN IP"
new-connection-mark="Hairpin NAT" src-address-list=LAN_IP
/ip firewall nat
add action=masquerade chain=srcnat comment="Hairpin NAT" connection-mark=
"Hairpin NAT"
add action=masquerade chain=srcnat comment="defconf: masquerade"
ipsec-policy=out,none out-interface-list=WAN
add action=dst-nat chain=dstnat comment="https for reverse proxy"
dst-address-list="WAN IP" dst-port=443 protocol=tcp src-address-list=
"!Block List" to-addresses=10.2.1.8 to-ports=443
/ip firewall raw
add action=drop chain=prerouting comment="Block DNS Flooding" dst-port=53
in-interface=lte1 protocol=udp
/ip firewall service-port
set ftp disabled=yes
set tftp disabled=yes
set sip disabled=yes
/ip service
set ftp disabled=yes
set telnet disabled=yes
set www disabled=yes
/ipv6 firewall address-list
add address=::/128 comment="defconf: unspecified address" list=bad_ipv6
add address=::1/128 comment="defconf: lo" list=bad_ipv6
add address=fec0::/10 comment="defconf: site-local" list=bad_ipv6
add address=::ffff:0.0.0.0/96 comment="defconf: ipv4-mapped" list=bad_ipv6
add address=::/96 comment="defconf: ipv4 compat" list=bad_ipv6
add address=100::/64 comment="defconf: discard only " list=bad_ipv6
add address=2001:db8::/32 comment="defconf: documentation" list=bad_ipv6
add address=2001:10::/28 comment="defconf: ORCHID" list=bad_ipv6
add address=3ffe::/16 comment="defconf: 6bone" list=bad_ipv6
/ipv6 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 ICMPv6" protocol=
icmpv6
add action=accept chain=input comment="defconf: accept UDP traceroute"
dst-port=33434-33534 protocol=udp
add action=accept chain=input comment=
"defconf: accept DHCPv6-Client prefix delegation." dst-port=546 protocol=
udp src-address=fe80::/10
add action=accept chain=input comment="defconf: accept IKE" dst-port=500,4500
protocol=udp
add action=accept chain=input comment="defconf: accept ipsec AH" protocol=
ipsec-ah
add action=accept chain=input comment="defconf: accept ipsec ESP" protocol=
ipsec-esp
add action=accept chain=input comment=
"defconf: accept all that matches ipsec policy" ipsec-policy=in,ipsec
add action=drop chain=input comment=
"defconf: drop everything else not coming from LAN" in-interface-list=
!LAN
add action=fasttrack-connection chain=forward comment="defconf: fasttrack6"
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 packets with bad src ipv6" src-address-list=bad_ipv6
add action=drop chain=forward comment=
"defconf: drop packets with bad dst ipv6" dst-address-list=bad_ipv6
add action=drop chain=forward comment="defconf: rfc4890 drop hop-limit=1"
hop-limit=equal:1 protocol=icmpv6
add action=accept chain=forward comment="defconf: accept ICMPv6" protocol=
icmpv6
add action=accept chain=forward comment="defconf: accept HIP" protocol=139
add action=accept chain=forward comment="defconf: accept IKE" dst-port=
500,4500 protocol=udp
add action=accept chain=forward comment="defconf: accept ipsec AH" protocol=
ipsec-ah
add action=accept chain=forward comment="defconf: accept ipsec ESP" protocol=
ipsec-esp
add action=accept chain=forward comment=
"defconf: accept all that matches ipsec policy" ipsec-policy=in,ipsec
add action=drop chain=forward comment=
"defconf: drop everything else not coming from LAN" in-interface-list=
!LAN
/system clock
set time-zone-name=Europe/London
/system identity
set name=Berthllwyd_ATL
/system scheduler
add !days interval=1w1d name=MalwareFullUpdate policy=
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon
start-date=2026-09-30 start-time=09:39:33
add !days interval=12h name=MalwareDiffUpdate policy=
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon
start-date=2026-09-30 start-time=09:39:58
/tool mac-server
set allowed-interface-list=LAN
/tool mac-server mac-winbox
set allowed-interface-list=LAN
/tool sms
set port=lte1 receive-enabled=yes

Thank you in advance

Jon

Ok, so I've been doing a bit more digging when time in the chair allows as I've not long had hip replacement.

If I wireshark the traffic on my wlan interface I can see my request going out to the correct public IP following a DNS request and I can see the traffic coming back from the public IP.

So my next question is why does hairpin NAT seemingly break my certificates on the reverse proxy? These are LE certificates generated via DNS challenge to my DDNS provider and works fine externally.

Hi,

I don't like these 2 rules. (But I assume they may work)

I think perhaps a src nat rule where it uses the dst-address-list and src-address list directly might be better. And perhaps src-nat to a known fixed IP address you can then find in your proxy logs.
(Src nat rule only runs once per connection)

You could edit the hosts file on your computer, so one or more of the hairpinned hosts will connect from your computer directly at the proxy (10.2.1.8) and then try connecting to one of these hosts and see if it works or you are still getting errors, and what they are.

eg.
in hosts

10.2.1.8   cctv.tsilimawr.co.uk

Thanks for your reply. Those two rules were removed and I went back to the standard set up shown in several places and on a MikroTik video.

I could see Wireshark traffic correctly when NAT'd i.e correct source address on server and correct dst address at client so I think the NAT was working fine. I have since set up Split DNS on my Technitium server and that appears to be giving the same result with the browser saying that secure connection failed.

I need to dig in to Nginx Proxy Manager I think and see what the problem is there as I have a wildcard cert using DNS challenge that renews fine.

Of course all this used to work fine on my TP Link 4G device but I have ditched that as it was old and not 5G capable, not that we have 5G yet in our rural setting but one can hope. It also didn't do VLANs or allow control over the firewall rules. Luckily I'm off work until the end of October so I have nothing but time to resolve this :smiley:

Everything works fine once I'm external so it has to be a local thing with Nginx I think.

So, I found the problem. I did some more digging with Wireshark and could see traffic not reaching the application and the NPM was responding with a 444.

After spending days and my brain getting fried I asked on the Github for the docker container I'm using. They pointed me in the direction of the geoblocking I had enabled.

Once I looked at that I could see the issue and I allowed my private IP range that of course has no country code.

It worked fine without that on my TPLink router so I can only assume they weren't doing srcnat in the same way and all traffic had my public IP even when accessed internally.

That makes sense.
Many devices do hairpin NAT using their external WAN IP address as source, there doesn't seem to be a simple obvious way to do this on a Mikrotik (where WAN IP is dynamic).