Australian cloud for business software — built and run in Sydney since 2003

NetLeverage
NetLeverage

Port Forward Works From the Internet but Not Inside: NAT Hairpin or Split DNS

Technical article
Troubleshooting

Direct answer: when a service published with a port forward works from the internet but fails for clients on the same internal network that use its public name, the usual cause is that the edge device does not hairpin. There are two ways it goes wrong:

  • The internal client's packet to the public address never gets translated back to the internal server.
  • It gets translated with DNAT only, so the server replies directly to the client from its private address. The client is waiting for a reply from the public address and discards it.

Prove which case you have with a capture on the server and on the client. Then pick one of three fixes:

  1. Hairpin NAT. DNAT plus SNAT for internal sources. This works for any client, but the server sees the firewall's address instead of the real client address.
  2. Split-horizon DNS. The same name resolves to the internal address for internal clients. The server keeps seeing real client addresses, but you now maintain two answers.
  3. A direct internal name. This works only if the TLS certificate and the application accept that name.

Whichever you choose, keep the name the client uses covered by the server's TLS certificate.

Why it matters

This fault often turns up right after something new is published, such as a portal, a remote access gateway or an API. External testing passes, and then staff in the office report that "the site is down". Because it works from a phone on mobile data, the issue gets bounced between the application and network teams.

A partial fix can make things worse:

  • A hosts-file entry on one PC hides the fault and goes stale when addresses change.
  • A DNAT rule without matching SNAT produces intermittent resets that look like application faults.
  • An internal name the certificate does not cover turns a connection failure into certificate warnings that users learn to click through.

The DNS cutover article under Related articles covers a different problem: clients reaching an old server because of caches and TTLs after a move. This article is about a steady-state topology fault, where the internal path to the published address is broken.

How it works

Take a typical small layout:

  • Internal network 192.168.1.0/24.
  • Firewall inside address 192.168.1.1.
  • Public address 203.0.113.10 on the firewall.
  • Web server 192.168.1.120, published on TCP 443 with a DNAT rule on the outside interface.

(The 203.0.113.0/24 range is reserved for documentation.)

From the internet.

  1. A client at 198.51.100.7 connects to 203.0.113.10:443.
  2. The firewall translates the destination to 192.168.1.120 and forwards the packet.
  3. The server replies to 198.51.100.7 through its default gateway, which is the firewall.
  4. The firewall reverses the translation, and the client sees replies from 203.0.113.10. This works.

From inside, with no hairpin support.

The client at 192.168.1.50 resolves the public name to 203.0.113.10 and sends the packet to the firewall. If the DNAT rule only matches traffic arriving on the outside interface, nothing translates the packet. The firewall drops it, or delivers it to its own service on that address, such as its management page.

From inside, with DNAT only.

  1. The firewall matches the packet and translates the destination to 192.168.1.120.
  2. The server sees a packet from 192.168.1.50, which is on its own subnet.
  3. The server replies directly to the client from 192.168.1.120, bypassing the firewall.
  4. The client sent its SYN to 203.0.113.10 and receives a SYN-ACK from 192.168.1.120. That reply does not belong to any connection it opened, so it is discarded or answered with a reset. The connection never completes.

Hairpin NAT.

The firewall also translates the source of internal-to-public-address traffic, to its inside address (192.168.1.1). The server then replies to the firewall, and the firewall reverses both translations.

RFC 4787 (UDP) and RFC 5382 (TCP) describe this behaviour as "hairpinning". They require that a NAT support it, and that the hairpinned packet carry the client's "External source IP address and port", meaning its public mapping. The inside-address SNAT shown here is a common way to make hairpinning work on a firewall that publishes a server; it is an illustration, not the exact behaviour the RFCs specify. Many devices make hairpin support a separate setting or rule. The cost is that the server's logs, and any rule based on source IP, now see the firewall's address for internal clients.

Split-horizon DNS avoids the round trip entirely. Internal resolvers answer the public name with 192.168.1.120, and external resolvers answer with 203.0.113.10. Windows Server 2016 and later can do this on a single DNS server with DNS policies and zone scopes. Microsoft's split-brain DNS guides show a policy that selects the internal zone scope for queries received on the internal interface.

Microsoft notes two limitations for Active Directory-integrated zones:

  • DNS policies are not AD-integrated, so they are not replicated to the other DNS servers hosting the zone.
  • Dynamic updates and scavenging work only on the default zone scope.

Read-only checks

None of these was run for this article. Captures need administrator or root rights and record traffic, so filter them narrowly and handle the files under your data-handling rules.

1. What does the internal client resolve?

Resolve-DnsName <public-name>
nslookup <public-name>

If internal clients get the public address, the hairpin path is in use. If they get the internal address, split DNS is already in place and the fault is somewhere else.

2. What does the client see? Try a TCP connection from an internal client:

Test-NetConnection <public-name> -Port 443

TcpTestSucceeded : False from inside, with True from outside, matches this fault.

3. Capture on the server during an internal attempt. At the server, an internal client's packet arrives after the firewall has translated it. Its destination is already the server's own address (192.168.1.120). Its source is the client (192.168.1.50) with DNAT only, or the firewall (192.168.1.1) with working hairpin NAT. Filter for both possible sources, and stop the capture once the attempt is over. On a Linux server, for example:

tcpdump -ni <interface> -c 200 'tcp port 443 and (host 192.168.1.50 or host 192.168.1.1)'
What the server shows Meaning
Nothing arrives from either address Candidate: no hairpin translation. Routing, a firewall rule or a capture on the wrong interface can look the same. Check the firewall's rule and session counters for the attempt, and capture on its inside interface too
SYN arrives from 192.168.1.50 and the server sends SYN-ACK directly to it DNAT only, so the reply bypasses the firewall
SYN arrives from 192.168.1.1 (the firewall) and the handshake completes Hairpin NAT is working, so look at the application or TLS

4. Capture on the client. A SYN-ACK from 192.168.1.120 answering a SYN sent to 203.0.113.10, followed by a reset or a retransmitted SYN, confirms the DNAT-only case.

5. Read the firewall rules. On Linux with nftables:

nft list ruleset

Look at the nat table. Does the prerouting DNAT rule match only on the outside interface (iif)? Is there a postrouting SNAT or masquerade rule that covers internal sources heading to the server?

On a commercial firewall, look up the vendor's hairpin, "NAT reflection" or "loopback" setting in its own documentation. This article does not cover vendor-specific options.

Interpreting the results

Finding Conclusion Preferred fix
Clients resolve the public address; nothing reaches the server from the client or firewall address Likely no hairpin translation, once firewall counters and an inside-interface capture show the packet is not forwarded Add hairpin NAT, or split DNS
Server replies directly to the client's private address DNAT without SNAT Add the SNAT half for internal sources, or switch to split DNS
Hairpin works, but logs show every internal user as the firewall address Expected cost of hairpin NAT Accept it, or use split DNS if the application needs the real client IP
Internal clients resolve the internal address but get certificate warnings Possibly a name the certificate does not cover; the warning can also be a chain or expiry problem, or a different server answering Read the exact validation error and which endpoint presented the certificate. For a name mismatch, use the public name with split DNS or add the internal name to the certificate
Some internal resolvers return the internal address and others the public address Candidate: the split DNS policy is missing on some servers (DNS policies are not AD-replicated). Cached answers or a different zone or forwarder can also explain it Compare the policies, zone scopes and cache on each resolver clients use, then apply the same policy to every one
Works on wired, fails on guest Wi-Fi Different resolvers or network segments Check which resolvers and NAT rules apply to that segment

Decision guidance

Option A: hairpin NAT (DNAT plus SNAT for internal sources)

Choose this when you cannot control internal DNS, or when the clients are devices using public resolvers. On nftables, the pattern is to extend the DNAT rule so it matches the public address from inside, and to add an SNAT rule limited to internal sources heading to the published server and port. The nftables wiki documents the building blocks:

  • dnat to in a prerouting chain.
  • snat to and masquerade in a postrouting chain.
  • The conntrack status bit dnat (ct status dnat), which matches connections that were destination-NATed.

An illustrative rule set, using the documentation addresses above:

# prerouting: translate the public address whichever side it arrives on
ip daddr 203.0.113.10 tcp dport 443 dnat to 192.168.1.120
# postrouting: only for internal clients reaching the published server through the DNAT
ip saddr 192.168.1.0/24 ip daddr 192.168.1.120 tcp dport 443 ct status dnat snat to 192.168.1.1

Adapt the table and chain names to your existing rule set. Keep the SNAT rule this narrow, so that ordinary LAN traffic to the server is not rewritten.

  • Verification: the same server capture as in check 3 shows internal handshakes from 192.168.1.1 completing, and the internal Test-NetConnection succeeds. Check that external access still works.
  • Rollback: delete the two added rules by handle (nft -a list ruleset shows the handles), or restore the saved rule set.

Option B: split-horizon DNS

Choose this when you control the resolvers internal clients use, and the server needs real client addresses for logging, rate limits or access rules.

  • On Windows DNS, either use DNS policies with an internal zone scope, applied to every DNS server, or create an internal zone for the exact published name that holds a single record for the internal address.
  • The second approach avoids taking authority for the whole public domain internally. If you do host the whole public zone internally, every public record now has to be maintained in two places.

Verification: Resolve-DnsName from internal clients on every segment returns the internal address. External resolvers still return the public address. Browsers show a valid certificate for the public name.

Rollback: remove the policy, zone scope or internal zone, then flush client caches as needed.

Option C: a separate internal name

This is acceptable only if the certificate covers that name and the application is happy to answer to it. Many web applications generate absolute links or redirects to the configured public name, so users bounce back to the public address. Check with a full sign-in and navigation test, not just the home page.

TLS in every case

Clients validate the certificate against the name they typed, not the address it resolves to. Hairpin NAT and split DNS both keep the public name, so the existing certificate stays valid. For checking the live endpoint after any change, see the IIS TLS renewal article under Related articles.

What not to do

  • Do not fix individual PCs with hosts-file entries.
  • Do not add a broad SNAT or masquerade rule for all LAN-to-LAN traffic to make hairpinning work. It hides every client address and can break other services.
  • Do not take authority internally for the whole public domain just to override one name, unless you are prepared to maintain every record in both places.
  • Do not issue an internal-name certificate and tell users to ignore warnings in the meantime.
  • Do not assume a DNS policy on one domain controller covers clients that use another DNS server (Microsoft: DNS policies are not replicated).

Limitations

  • Examples cover IPv4 NAT and nftables. Each commercial firewall's hairpin option is documented by its vendor and is not covered here.
  • With IPv6 and no NAT, the published address is usually the server's own. This specific hairpin problem does not arise, but firewall and DNS design still apply.
  • The rules shown are illustrative, using RFC 5737 documentation addresses, and need adapting to the existing table and chain structure.

Acceptance checklist

  • [ ] Internal resolution result recorded for each client segment
  • [ ] Server and client captures identify the case: no hairpin, DNAT only, or working
  • [ ] Fix chosen (hairpin NAT, split DNS or internal name) with the reason, including whether real client IPs are needed
  • [ ] Any SNAT rule limited to internal sources, the published server and the port
  • [ ] Split DNS policy present on every DNS server internal clients use
  • [ ] Internal and external Test-NetConnection succeed, and the browser shows a valid certificate for the public name
  • [ ] Previous rule set or DNS configuration saved for rollback

References

All accessed 2 November 2026.

  • RFC 4787, NAT Behavioral Requirements for Unicast UDP (section 6 Hairpinning, REQ-9): https://www.rfc-editor.org/rfc/rfc4787.txt
  • RFC 5382, NAT Behavioral Requirements for TCP (section 7.2, REQ-8): https://www.rfc-editor.org/rfc/rfc5382.txt
  • RFC 7857, Updates to NAT Behavioral Requirements (hairpinning for ICMP): https://www.rfc-editor.org/rfc/rfc7857.txt
  • nftables wiki, Performing Network Address Translation (NAT): https://wiki.nftables.org/wiki-nftables/index.php/Performing_Network_Address_Translation_%28NAT%29
  • nftables wiki, Matching connection tracking stateful metainformation (ct status): https://wiki.nftables.org/wiki-nftables/index.php/Matching_connection_tracking_stateful_metainformation
  • Microsoft Learn, Use DNS Policy for Split-Brain DNS Deployment: https://learn.microsoft.com/en-us/windows-server/networking/dns/deploy/split-brain-dns-deployment
  • Microsoft Learn, Use DNS Policy for Split-Brain DNS in Active Directory: https://learn.microsoft.com/en-us/windows-server/networking/dns/deploy/dns-sb-with-ad

Related articles

Learning notes

  1. A DNAT-only forward breaks for clients on the server's own subnet, because the reply goes back directly from the private address.
  2. Hairpin NAT adds SNAT for internal sources. It works for every client but hides the real client address.
  3. Split-horizon DNS keeps real client addresses, but must be applied on every resolver clients use. Windows DNS policies are not AD-replicated.
  4. Prove the case with a server capture: nothing arriving, a direct reply, or the firewall as the source.
  5. Keep the public name in use so the TLS certificate stays valid, and test a full sign-in flow, not just the home page.