Skip to main content
RunBook Academy

OPNsenseXIII · Port Forwarding and NAT ReflectionNAT reflection

NAT reflection and hairpin NAT — when it is needed and why split DNS is safer

Advanced⏱ ~13 minpfctldrill

What you'll learn

  • Explain what NAT reflection (hairpin) is and when it is required
  • Enable and disable NAT reflection safely
  • Recognise the security implications of hairpin NAT
  • Choose between NAT reflection and split DNS for internal access to port-forwarded services

Prerequisites

Verified against OPNsense 25.x · FreeBSD 14.x · PF (FreeBSD packet filter) FreeBSD 14.x · Unbound 1.20+ · Kea DHCP OPNsense 25.x plugin · WireGuard in-kernel + OPNsense plugin · strongSwan (IPsec plugin) OPNsense 25.x plugin · OpenVPN 2.6.x · Suricata 7.x · 2026-08-14

Not yet marked complete on this device.

A port forward maps a public IP to an internal server. External clients use the public IP to reach the service. Internal clients, however, are on the LAN — they have a direct route to the internal server. If an internal client tries to reach the service by its public IP, the packet leaves the LAN, goes out the WAN, comes back to the firewall, gets DNAT’d, and arrives at the internal server. The round trip works, but the path is bizarre: the packet traverses the WAN link twice for what is logically a LAN-side connection.

The result is two visible failure modes. First, the round trip may fail if the WAN link drops or the upstream ISP blocks hairpin traffic. Second, the performance is poor — internal traffic to a “local” service takes the long way around.

NAT reflection (also called hairpin NAT) is the mechanism that fixes this: the firewall detects that an internal client is trying to reach the firewall’s own public IP, applies the translation, and forwards the packet to the internal server via the LAN. The packet never leaves the LAN.

This lesson covers what NAT reflection is, when it is required, and why split DNS is the cleaner alternative.

The hairpin problem

The setup: the firewall’s WAN IP is 198.51.100.1. A web server at 192.0.2.10 is exposed via port forward on TCP/443. An internal client at 192.0.2.50 wants to reach the web server by public IP (https://198.51.100.1).

Without NAT reflection, the packet path:

  1. Client 192.0.2.50 sends a SYN to 198.51.100.1:443.
  2. The packet leaves the LAN to the firewall’s WAN.
  3. The firewall receives the packet on WAN, evaluates the rdr rule, rewrites destination to 192.0.2.10:443.
  4. The firewall routes the packet back to 192.0.2.10 via LAN.
  5. The server replies. The reply goes to 192.0.2.50 (the client’s LAN IP, because the server sees the original source).
  6. The client receives the reply with source 198.51.100.1.

This works in theory. In practice, it fails for three reasons:

  • The WAN ISP may block hairpin. Some ISPs drop traffic with source = destination IP (the WAN IP) because the packet leaves and re-enters on the same interface.
  • The path is slow and inefficient. Internal traffic takes the WAN path twice.
  • Asymmetric routing breaks state. The packet leaves via WAN, returns via LAN. PF state may not match the return traffic if the firewall treats the path as asymmetric.

NAT reflection fixes all three by handling the traffic entirely on the LAN side.

What NAT reflection does

NAT reflection detects when an internal host sends traffic to the firewall’s own public IP and rewrites the packet to go directly to the internal server via the LAN. The packet never leaves the LAN.

The packet path with NAT reflection:

  1. Client 192.0.2.50 sends a SYN to 198.51.100.1:443.
  2. The firewall receives the packet on LAN. PF recognises the destination 198.51.100.1 as the firewall’s own WAN IP.
  3. PF applies the rdr rule (which would normally apply on the WAN). The destination is rewritten to 192.0.2.10:443.
  4. PF routes the rewritten packet to 192.0.2.10 via LAN.
  5. The server replies. The reply is routed back through the firewall.
  6. PF matches state and reverse-translates. The client receives the reply.

The packet stays on the LAN the entire time. The WAN link is not traversed.

Read-only / SafeNAT reflection enabled
$ pfctl -s nat -v | grep -A 1 'reflect'
@8 rdr on igb1 inet proto tcp from any to any port = 443 -> 192.0.2.10 port 443
 [ nat reflection enabled for 192.0.2.0/24 ]

Illustrative output

Configuring NAT reflection

NAT reflection is configured under Firewall → Settings → Advanced → NAT Reflection. There are three modes:

  • Disable NAT Reflection. No hairpin. Internal clients cannot reach port-forwarded services by public IP.
  • Enable NAT Reflection (NAT + proxy). PF handles the reflection using NAT rules. Recommended for most cases.
  • Enable NAT Reflection (proxy only). A user-space proxy handles the reflection. Used for specific legacy scenarios.

The default is Disable. Operators must opt in.

Per-port-forward reflection can also be enabled in the port forward’s settings. The two settings interact: the global setting is the default; the per-rule setting overrides for that port forward.

The security implications of NAT reflection

NAT reflection has three security implications:

  1. Internal exposure surface increases. Every port-forwarded service becomes reachable from the internal network by its public IP. If the service has weaker internal security (e.g. it assumes LAN clients are trusted), hairpin breaks that assumption. The internal client now reaches the service as if it were external, with all the authentication and rate limits the external interface enforces — but the service may have been configured to trust internal traffic and skip those checks.

  2. State and session complexity. Hairpin traffic traverses the firewall twice — once on the way in, once on the way out. PF state tracking is correct, but the volume of state entries doubles for hairpin flows. On a busy firewall with many port forwards and many internal clients, this is a measurable load increase.

  3. Confusion during incident response. When an internal client reports “the service is broken”, the operator may not realise the client is reaching the service via hairpin. The packet path is different from the external path; the firewall logs are different. Diagnosing hairpin issues requires understanding the reflection ruleset.

Why split DNS is preferred

Split DNS — also called split-horizon DNS — is the alternative to NAT reflection that most production estates adopt. Instead of making internal clients reach the public IP via hairpin, the internal DNS resolver returns the private IP for the same hostname.

The configuration:

  • External DNS for service.example.com returns 198.51.100.1 (the public IP).
  • Internal DNS for service.example.com returns 192.0.2.10 (the internal IP).

Internal clients resolving service.example.com get the private IP. They reach the service directly via LAN. The WAN path is not traversed. The firewall is not in the path.

The advantages:

  • Performance. LAN-direct traffic is faster than hairpin.
  • Simplicity. No NAT reflection rules to manage. The firewall configuration is simpler.
  • Security. Internal clients reach the service directly. The service sees the client’s real internal IP. Authentication and logging work correctly. No assumption that the client is external.
  • No WAN dependency. Internal access to the service does not require the WAN link to be up.

The disadvantages:

  • DNS configuration required. Split DNS requires the internal DNS resolver to return different answers than the external resolver. The configuration is in the DNS server (typically Unbound on OPNsense).
  • Application-level caching can break split DNS. A client that resolved the hostname externally (e.g. while on a VPN) may cache the public IP and use it after reconnecting to the LAN. TTLs and resolver configuration affect this.

When NAT reflection is the right choice

Three situations favour NAT reflection over split DNS:

  • Clients cannot be configured to use split DNS. Legacy applications that hard-code the public IP, embedded systems that do not respect DNS, hard-coded https://... URLs in device firmware.
  • The service is referenced by IP, not hostname. Some clients reach services by IP rather than DNS. Split DNS does not help when the IP is hard-coded.
  • The internal DNS infrastructure is not available. A remote site with no internal DNS, a temporary network, or a test environment.

In all other cases, split DNS is the cleaner choice.

A worked example

The setup: a web service at service.example.com is exposed on TCP/443 via port forward. The firewall’s WAN IP is 198.51.100.1. The internal server is 192.0.2.10. Internal DNS resolver is Unbound on OPNsense.

Option A: NAT reflection only.

# GUI: Firewall → Settings → Advanced → NAT Reflection = "Enable NAT Reflection"

Result: internal clients resolve service.example.com to 198.51.100.1 (assuming they use external DNS or have no split DNS). They reach the firewall’s WAN IP. Hairpin rewrites the destination to 192.0.2.10. The server replies. The connection works.

Option B: Split DNS only.

# GUI: Services → Unbound DNS → Overrides → Add
# Host: service.example.com
# Domain: example.com
# Type: A
# Value: 192.0.2.10
# Description: Internal override for service.example.com

Result: internal clients resolve service.example.com to 192.0.2.10 via Unbound. They reach the server directly. The firewall is not in the path. WAN reflection is irrelevant.

Option C: Both (defence in depth).

NAT reflection enabled globally, plus split DNS configured. If a client somehow resolves the public IP, hairpin catches it. If split DNS works, the client uses the internal IP directly.

The cleanest production pattern is Option B (split DNS only) with NAT reflection disabled. It is simpler, faster, and avoids the security implications of hairpin. Option C is for cases where some clients cannot use split DNS.

Summary

  • NAT reflection (hairpin) lets internal clients reach port-forwarded services by public IP without traversing the WAN link.
  • The firewall detects internal traffic to its own WAN IP and rewrites it to the internal server via the LAN.
  • NAT reflection is opt-in and applies to all port forwards when enabled globally.
  • Split DNS returns the private IP for internal clients and is the preferred alternative in most production deployments.
  • Use NAT reflection only when split DNS is not feasible (hard-coded IPs, no internal DNS, legacy clients).

Knowledge check · 4 questions

  1. Q1. An internal client at 192.0.2.50 tries to reach `https://service.example.com`, which resolves to the firewall's public IP 198.51.100.1. The firewall has the port forward configured. NAT reflection is disabled. What happens?

  2. Q2. NAT reflection is enabled by default on OPNsense.

  3. Q3. Which of the following are valid reasons to prefer split DNS over NAT reflection? Select all that apply.

  4. Q4. You need to support legacy clients that hard-code the public IP `198.51.100.1` and cannot be reconfigured. Split DNS is not feasible. The right choice is:

Passing score: 75%. Answers are checked in this browser.