Solving DNAT Loopback Access: How to Access Local Web Server via Public IP from Inside LAN


3 views

When configuring a network with port forwarding (DNAT) for external access to internal servers, a common issue arises when trying to access these same services via the public IP from within the local network. This is known as the "NAT loopback" or "hairpin NAT" problem.

The core issue occurs because:

  • Internal client sends request to public IP
  • Router's NAT rules don't properly redirect internal traffic
  • Traffic either gets dropped or fails to route back to internal network

For OpenWRT routers with iptables, we need to add specific rules to handle internal traffic:

# Add to /etc/firewall.user
iptables -t nat -A PREROUTING -i br-lan -d PUBLIC_IP -p tcp --dport 80 -j DNAT --to 192.168.2.10
iptables -t nat -A POSTROUTING -o br-lan -s 192.168.2.0/24 -d 192.168.2.10 -j MASQUERADE

For those preferring DNS solutions, dnsmasq can be configured to override certain domains:

# Add to /etc/dnsmasq.conf
address=/yourdomain.com/192.168.2.10
address=/subdomain.yourdomain.com/192.168.2.10

After implementing changes, verify with:

iptables -t nat -L -n -v
ping yourdomain.com  # Should resolve to internal IP
curl -I http://yourdomain.com  # Should return server headers

The NAT-based solution adds minimal overhead since:

  • Rules are processed in kernel space
  • Only affects traffic destined for specific IPs
  • Doesn't impact external-to-internal traffic flow

For more complex setups consider:

  • Split DNS configuration
  • Proxy server on the router
  • Network namespace isolation

When configuring public-facing services on a local network, many administrators encounter the classic "hairpin NAT" problem. The scenario:

  • External requests via public IP (DNAT) work perfectly
  • Internal requests to the same public IP mysteriously fail
  • DNS resolves correctly, but routing breaks within LAN

The root cause lies in how the router handles packets when both source and destination are within the same network segment. In your OpenWRT setup with this iptables rule:

-A PREROUTING -i ppp0 -p tcp -m multiport --dports 22,25,80,443 -j DNAT --to-destination 192.168.2.10

The key limitation is that it only processes incoming traffic on ppp0, completely ignoring internal requests coming from br-lan.

Here's the complete iptables implementation for OpenWRT that solves this:

# Enable NAT reflection
iptables -t nat -A PREROUTING -d PUBLIC_IP -j DNAT --to-destination 192.168.2.10
iptables -t nat -A POSTROUTING -s 192.168.2.0/24 -d 192.168.2.10 -j MASQUERADE

# For specific ports (recommended for security)
iptables -t nat -A PREROUTING -i br-lan -p tcp --dport 80 -d PUBLIC_IP \
  -j DNAT --to-destination 192.168.2.10:80
iptables -t nat -A POSTROUTING -o br-lan -p tcp --dport 80 -s 192.168.2.0/24 \
  -d 192.168.2.10 -j MASQUERADE

For environments with many domains/subdomains (as mentioned in your case), consider DNS-level resolution:

# In dnsmasq.conf (OpenWRT default DNS)
address=/yourdomain.com/192.168.2.10
address=/.subdomain.yourdomain.com/192.168.2.10

# For multiple domains
address=/domain1.com/192.168.2.10
address=/domain2.net/192.168.2.10

The most robust solution combines NAT reflection with DNS manipulation:

# 1. Configure dnsmasq wildcards (OpenWRT specific)
echo "address=/#.example.com/192.168.2.10" >> /etc/dnsmasq.conf
/etc/init.d/dnsmasq restart

# 2. Set up generic reflection NAT
iptables -t nat -N REFLECT
iptables -t nat -A REFLECT -j DNAT --to-destination 192.168.2.10
iptables -t nat -A PREROUTING -d PUBLIC_IP -j REFLECT
iptables -t nat -A OUTPUT -d PUBLIC_IP -j REFLECT

After implementation, verify with these commands:

# Check NAT rules
iptables -t nat -L -v -n

# Test DNS resolution from LAN
nslookup example.com 192.168.2.1

# Verify packet flow
tcpdump -i br-lan host 192.168.2.10 and port 80