What Is NAT? SNAT, DNAT, and the Translation Table
What NAT really does, how SNAT and DNAT differ, how PAT reuses one address, why double NAT and CGNAT break port forwarding, and a measured look at hairpin NAT.
- What Is NAT? The Two Halves of Address Translation
- What Does NAT Stand For?
- Two of the Five-Tuple Change
- Why Does NAT Exist? IPv4 Scarcity and Private Address Blocks
- What Are the Types of NAT? Static, Dynamic, and PAT
- What Is the NAT Table? The Ledger Where Translation Is Kept
- Lab: Two Separate Networks on One Machine
- First, Without NAT: The Packet Leaves but Doesn’t Return
- Seeing the Translation From Both Sides at Once
- Reading the Ledger: Two Tuples, One Line
- The Ledger’s Lifespan: When Is an Entry Deleted?
- What Is PAT? One Address, Thousands of Connections
- The Same Inside Port, Two Different Connections: Which Does NAT Change?
- What Is the Difference Between SNAT and DNAT?
- What Is Port Forwarding? How a NAT Rule Is Written
- What Are Double NAT and CGNAT?
- Lab: Putting Two Translations in a Row
- Why One Rule Isn’t Enough
- CGNAT: When You’re Not the One Translating
- Will IPv6 Put an End to NAT?
- NAT Diagnosis: Server Is Up but Unreachable From Outside
- Hairpin NAT: Reaching Your Own Public Address From Inside
- When the External Identity Changes
- Conclusion
NAT does two jobs, and the second is skipped in most explanations: it changes the source address of an outbound packet, and then it writes that change into a ledger. Without the ledger, translation would be a one-way trick; when the reply came back, no one would know who had asked for it, and the packet would die at the edge. Understanding NAT is not knowing that the address changes. It is knowing what that ledger holds.
In the network segmentation guide we compared the edge of a network to passport control: inside your own country nobody asks for your papers at every step, but at the border your local identity isn’t enough and you’re handed a passport that works abroad. That telling stood on the traveler’s side of the border. This article walks into the booth, because that is where the real mechanism sits: in the ledger open on the officer’s desk.
Below, we actually open that ledger. Every command and every output here was taken in our own lab; none of it is carried over from anywhere else. On a single Linux machine we stood up a small NAT gateway, captured both sides of the translation at the same moment, printed the translation table itself, and measured how two connections leaving from the same inside port get told apart on the way out. At the end there is a fresh measurement of hairpin NAT, the case that most often gets diagnosed wrong.
What Is NAT? The Two Halves of Address Translation
NAT (Network Address Translation) is the mechanism that changes address information on a device a packet passes through and keeps that change in a table. The changing part is easy; the keeping-a-table part is what makes NAT what it is.
Picture the officer at the border. The job isn’t only to hand you a passport. The moment it’s handed over, a line goes into a ledger: “with this passport, at this time, this citizen left.” When an answer comes back from outside, the officer looks at that ledger and finds who the answer belongs to. Close the ledger and the passport is useless: people can walk out, but none of them can come back.
We stay in this scene for the whole article. The correspondences, which we open one by one:
| At the border | On the network |
|---|---|
| Local identity | Private IP address |
| Passport | Public IP address |
| The officer’s ledger | The NAT table, i.e. connection tracking |
| Sequence number written in the ledger | Translated source port (PAT) |
| Identity changing on the way out | SNAT |
| A visitor saying “I’m here to see this person” | DNAT, i.e. port forwarding |
| A second passport in a transit country | Double NAT |
| No line in the ledger for that person | “Server is up but unreachable from outside” |
What Does NAT Stand For?
NAT stands for Network Address Translation. In a device interface or a document, seeing NAT is never ambiguous; the context makes it clear. Searching for the bare three letters is another matter, because the same acronym carries unrelated meanings in other fields; spelling out network address translation narrows results to the right area.
Two of the Five-Tuple Change
What identifies a TCP or UDP connection is not a single address but a tuple of five pieces of information: source address, source port, destination address, destination port, and protocol. Why that tuple identifies a connection rather than a person is something we cover in the port guide.
What NAT does is change not the whole tuple but two of its parts: the source side or the destination side. Which of the two it changes is the entire distinction between SNAT and DNAT that we’re about to reach.
The subtlety is this: the moment the tuple changes, the connection becomes a different connection for the party on the outside. The far end never sees your inside address, and can’t. The only thing it knows is the number on the passport.
Why Does NAT Exist? IPv4 Scarcity and Private Address Blocks
NAT was not designed as a security feature; it was designed as an answer to scarcity. The IPv4 address space holds about 4.3 billion addresses, and that is not large enough to give every device one.
The solution came in two parts. First, blocks that anyone can use freely but that are never routed on the internet were set aside: 10.0.0.0/8, 172.16.0.0/12, and 192.168.0.0/16. Second, those addresses had to be turned into a real address on the way out. That turning is NAT.
Put back on the metaphor, the picture sharpens: a local identity is valid inside a country and the same numbers can exist for everyone, because there is no international registry to compare them against. Crossing the border needs a document that is unique in the world. NAT is the translator joining those two worlds.
There is a side effect, and most of the rest of this article wrestles with it: the outside no longer knows you individually. Sometimes that is exactly what you want, and sometimes it is exactly the problem you are trying to solve.
What Are the Types of NAT? Static, Dynamic, and PAT
Address translation isn’t one thing; it’s the shared name of three different setups. What separates them is simple: how many inside addresses map to how many outside addresses, and is the mapping permanent?
| Type | Mapping | When it’s used |
|---|---|---|
| Static NAT | One inside address, always the same outside address. Permanent, written by hand | When an inside server has to be reachable from outside |
| Dynamic NAT | A free address is handed out from a pool; it returns to the pool when the connection ends | When you have a pool of outside addresses and fewer inside devices than the pool |
| PAT | One outside address, thousands of connections separated by port | Very nearly every home and office network today |
The border-booth equivalents are clean too. Static NAT is a passport number reserved for one person and never changing. Dynamic NAT hands out a free number from a pool at the desk and takes it back when the person returns. PAT hands the same passport to everyone and writes a sequence number beside it; the rest of the article is largely about this one, because it’s the setup you’ll meet in the field.
There is a fourth distinction, and it gets mistaken for a NAT type: how strictly the setup filters packets arriving from outside. Some setups, once you’ve connected to an address, accept every packet from that address; others accept only what comes from exactly the address and port you were talking to and drop the rest. The second is more secure, but in scenarios where both ends are behind NAT (video calls, gaming, peer-to-peer) it makes the connection harder to establish. This is why the same line runs smoothly for some applications and stalls on others; the problem isn’t bandwidth, it’s the filtering behavior at the edge.
What Is the NAT Table? The Ledger Where Translation Is Kept
Now let’s open the ledger. Every measurement below was taken on a real machine; both the commands and the outputs stand as they were.
Lab: Two Separate Networks on One Machine
Seeing a NAT gateway normally takes three things: an inside network, an outside network, and a device sitting between them. We can build all three inside a single Linux machine. Before the commands, three concepts, because without them the command lines look meaningless.
A network namespace is a separate network environment inside the machine. On Linux everything normally shares one network stack: the same interfaces, the same addresses, the same routing table. A namespace lets you split that. Each namespace you create has its own interfaces, its own IP address, and its own routing table, unaware of the rest of the machine. In practice it behaves like a small virtual machine, but without a separate operating system or a separate block of memory. We use namespaces in place of the devices on the inside network.
A veth pair is a virtual cable with two ends. A packet entering one end comes out the other. We put one end inside a namespace and the other on the machine itself. It’s the software equivalent of a physical cable, the thing that connects an inside device to the gateway.
The gateway is the machine itself. A virtual server running Ubuntu 24.04; both the door for the two inside networks and the side that leaves for the outside. It goes out through the ens33 interface with the address 192.168.1.15.
The setup, in a table:
| Role | Where it lives | Address | What it stands in for |
|---|---|---|---|
ic1 | Namespace | 10.10.1.2 | First device on the inside network |
ic2 | Namespace | 10.10.2.2 | Second device on the inside network |
| Gateway, inside faces | The machine itself | 10.10.1.1 and 10.10.2.1 | The devices’ default gateway |
| Gateway, outside face | The machine’s ens33 | 192.168.1.15 | The single address the outside sees |
| Target | A separate server | 192.168.1.10 | Standing in for “the outside world” |
Instead of an address on the internet, we use a server on our own network as the target. For the measurement it makes no difference: what matters for NAT is that the packet leaves the gateway and the reply comes back, not where it goes after that. We make the connection to port 389, the LDAP service, because that port is already open, so there’s a real party answering.
Now the setup. First the inside network: open a namespace and put one end of the virtual cable inside it.
ip netns add ic1
ip link add veth-gw1 type veth peer name veth-ic1
ip link set veth-ic1 netns ic1The three lines, in order: an isolated network environment named ic1 is opened, a two-ended virtual cable named veth-gw1 and veth-ic1 is created, then the veth-ic1 end of the cable is moved into ic1. From here on one end of the cable is on the machine, the other on the inside network.
Next, addressing. On the gateway end of the cable we write the gateway’s address; on the namespace end, the device’s address.
ip addr add 10.10.1.1/24 dev veth-gw1
ip link set veth-gw1 up
ip netns exec ic1 ip addr add 10.10.1.2/24 dev veth-ic1
ip netns exec ic1 ip link set veth-ic1 up
ip netns exec ic1 ip route add default via 10.10.1.1The ip netns exec ic1 prefix means “run this command inside ic1”; without it the command would apply to the machine’s own network environment. The last line points the inside device at its default gateway: hand every packet you don’t know where to send to 10.10.1.1.
Two things left. Let the machine take packets in on one interface and pass them out another, and tell it to translate the address on the way out.
sysctl -w net.ipv4.ip_forward=1
iptables -t nat -A POSTROUTING -s 10.10.0.0/16 -o ens33 -j MASQUERADEWithout the first line the machine won’t move an incoming packet to another interface; it silently drops traffic that isn’t its own. The second line is the one doing all the work, and it deserves reading piece by piece:
-t nat: write the rule into the NAT table, not the ordinary firewall table.POSTROUTING: apply it just before the packet leaves the machine.-s 10.10.0.0/16: touch only packets from the inside networks.-o ens33: touch only what leaves through the outside interface.MASQUERADE: replace the source address with the address of the interface it leaves through.
That last line is the passport desk itself. One note: on Ubuntu 24.04 the iptables command actually drives the nftables engine underneath, so the rule you write goes to the new backend, not the old one; the syntax and behavior are unchanged, so it doesn’t affect this measurement.
First, Without NAT: The Packet Leaves but Doesn’t Return
The standard line when this is taught in the field is: without NAT, the packet can’t get out. We set the experiment up precisely to test that sentence.
Three steps. We delete the translation rule we just wrote (-D instead of -A, remove instead of add). We put the outside interface into listening mode so we can see what actually leaves the machine. Then we try to connect from the inside network to the target.
iptables -t nat -D POSTROUTING -s 10.10.0.0/16 -o ens33 -j MASQUERADE
tcpdump -i ens33 -nn "host 192.168.1.10 and port 389"
ip netns exec ic1 nc -p 40000 -w 3 192.168.1.10 389The middle tcpdump line listens on ens33; -nn prints addresses and ports raw, without resolving names. The third line’s nc is the side making the connection: -p 40000 fixes the source port (we’ll see shortly why that matters), -w 3 tells it to give up after three seconds.
What shows up on the outside interface:
21:58:31.153681 IP 10.10.1.2.40000 > 192.168.1.10.389: Flags [S], seq 316966271
21:58:32.172475 IP 10.10.1.2.40000 > 192.168.1.10.389: Flags [S], seq 316966271
21:58:33.196472 IP 10.10.1.2.40000 > 192.168.1.10.389: Flags [S], seq 316966271The standard line didn’t hold. The packet left, three times over. The source address reads 10.10.1.2, so the private address was carried onto the outside interface as-is.
What’s instructive isn’t the lines that appear, it’s the ones that don’t: there’s no reply. The same packet repeating three times is the proof. The Flags [S] at the end of each line is TCP’s connection-opening SYN packet; with no acknowledgement from the far side, the same request is retried. How a connection is established and how this three-way handshake works step by step is covered in the port guide. The target server received the packet and tried to send its answer to 10.10.1.2; that address isn’t on its network, so the reply reached nowhere.
So the problem NAT solves isn’t “getting the packet out.” The packet already gets out. What NAT solves is letting the answer come back. If no passport were issued at the border, people could still walk out; they couldn’t return, because no one would take them back in.
Seeing the Translation From Both Sides at Once
Now we put the rule back and listen to the same connection on the inside cable and on the outside interface at the same time.
On the inside, i.e. before translation:
21:58:35.240881 IP 10.10.1.2.40000 > 192.168.1.10.389: Flags [S], seq 380829920
21:58:35.242910 IP 192.168.1.10.389 > 10.10.1.2.40000: Flags [S.], ack 380829921
21:58:35.243296 IP 10.10.1.2.40000 > 192.168.1.10.389: Flags [.], ack 1On the outside interface, i.e. after translation, in the very same second:
21:58:35.242021 IP 192.168.1.15.40000 > 192.168.1.10.389: Flags [S], seq 380829920
21:58:35.242902 IP 192.168.1.10.389 > 192.168.1.15.40000: Flags [S.], ack 380829921
21:58:35.243304 IP 192.168.1.15.40000 > 192.168.1.10.389: Flags [.], ack 1The only difference between the two dumps is the source address: 10.10.1.2 inside, 192.168.1.15 outside. The sequence numbers (seq 380829920) are identical, the timestamps match to the millisecond. So these two dumps are the same packet seen at two different points.
This is the metaphor exactly. The same traveler appears with a local identity inside the booth and with a passport outside it. The server on the outside will never in its life learn there’s an address called 10.10.1.2.
Reading the Ledger: Two Tuples, One Line
With the connection up, we read the translation table:
conntrack -L -d 192.168.1.10tcp 6 431997 ESTABLISHED src=10.10.1.2 dst=192.168.1.10 sport=40000 dport=389
src=192.168.1.10 dst=192.168.1.15 sport=389 dport=40000 [ASSURED] mark=0 use=1Reading this line is the key to understanding the whole table. There are two separate tuples in it:
- First tuple, the outbound direction:
src=10.10.1.2 dst=192.168.1.10 sport=40000 dport=389. What the inside device sent. - Second tuple, the expected return:
src=192.168.1.10 dst=192.168.1.15 sport=389 dport=40000. How the answer will look.
That’s exactly the ledger’s line: “this citizen left, the answer will come back on this passport.” The edge device compares every packet arriving from outside against this second tuple; if a matching line exists, it applies the translation in reverse and delivers the packet to its real owner.
The practical way to tell whether an entry has been translated is right here. Put the two tuples side by side: if they mirror each other, there’s no translation; if the mirror is broken, there is. As an example, a line from the machine’s own DNS traffic:
udp 17 27 src=192.168.1.15 dst=192.168.1.10 sport=55057 dport=53
src=192.168.1.10 dst=192.168.1.15 sport=53 dport=55057 mark=0 use=1Here the outbound tuple’s source is 192.168.1.15 and the return tuple’s destination is also 192.168.1.15. A perfect mirror, so this traffic wasn’t translated. In the TCP line above, the outbound source is 10.10.1.2 while the return destination is 192.168.1.15; the mirror is broken, so there was translation.
The Ledger’s Lifespan: When Is an Entry Deleted?
The numbers at the start of those lines aren’t coincidental. The TCP line reads 431997, the UDP line 27. These are countdowns to how many seconds until the entry is deleted.
The difference is large and the reason is sensible. In TCP the connection’s open and close are known, so the default lifetime for an established connection is close to five days. UDP has no such signal; the translating device can only infer the flow has ended from silence, so the counter starts around thirty seconds and resets with each packet.
What the metaphor says here is also true: the officer’s ledger doesn’t stay open forever, old lines close after a while. In practice this means a connection that stays silent for a long time is quietly forgotten at the edge device. Your session doesn’t drop, you get no error; it’s just that when the next packet arrives, the ledger is checked, no line is found, and the packet is dropped. This is the most common reason long-idle VPN and SSH sessions “drop for no reason,” and it’s exactly why keepalive settings exist: to keep that line alive.
What Is PAT? One Address, Thousands of Connections
So far we’ve followed a single inside device. In real life there are hundreds of devices behind that address, all going out at once. How does a single passport number stretch to all of them?
It doesn’t. What stretches is not the passport number but the sequence number written beside it. This form, which translates the port number along with the address, is called PAT (Port Address Translation), and it’s very nearly all of the NAT you see on home and office networks today.
The Same Inside Port, Two Different Connections: Which Does NAT Change?
The textbook answer here is clear: PAT changes the source port. To test it, we connect from two separate inside networks, from the exact same source port (40000), to the same target at the same time.
ip netns exec ic1 nc -p 40000 -w 6 192.168.1.10 389 &
ip netns exec ic2 nc -p 40000 -w 6 192.168.1.10 389 &What shows up on the outside interface:
21:58:35.242021 IP 192.168.1.15.40000 > 192.168.1.10.389: Flags [S], seq 380829920
21:58:35.242872 IP 192.168.1.15.50048 > 192.168.1.10.389: Flags [S], seq 2172617502The answer came out finer than expected. Both connections left from the same port; on the outside one stayed 40000, the other became 50048.
40000, but on the outside one stayed 40000 and the other became 50048. PAT rewrites the source port not always, but only when there's a collision.The translation table confirms it:
tcp 6 431997 ESTABLISHED src=10.10.1.2 dst=192.168.1.10 sport=40000 dport=389
src=192.168.1.10 dst=192.168.1.15 sport=389 dport=40000 [ASSURED]
tcp 6 431997 ESTABLISHED src=10.10.2.2 dst=192.168.1.10 sport=40000 dport=389
src=192.168.1.10 dst=192.168.1.15 sport=389 dport=50048 [ASSURED]The two lines’ outbound tuples are nearly identical, only the inside addresses differ. In the return tuples, the ports have split. So PAT doesn’t always change the port; it changes it when it’s forced to. The first connection could keep its port because that port was free on the outside. When the second arrived, the same port was in use and translation became unavoidable.
This behavior itself depends on the software doing the translating, and the measurement was taken on Linux netfilter; another vendor’s device might choose to rewrite the port in every case. The rule that doesn’t change: the same address-and-port pair on the outside can’t belong to two connections at once, because there’s no other information to tell the return packets apart.
The border equivalent is the same. Two people leaving on the same passport can’t be given the same sequence number; if they were, the officer wouldn’t know which one to hand the returning answer to. What makes the number unique isn’t the passport, it’s the passport and the number together.
Which is also why a single external address doesn’t identify anyone on its own. Thousands of devices can sit behind one outside address at the same instant, so tying a connection back to a single device isn’t done with the address; it takes the address, the port, and the time recorded together. A record kept without the port looks present in the file and can say nothing when it’s asked, like a ledger that wrote down the passport number but skipped the sequence number.
What Is the Difference Between SNAT and DNAT?
Every example so far went from inside to outside. What was translated was always the source address, and that is called SNAT (Source NAT). MASQUERADE is a special form of SNAT where the outside address is taken automatically from the interface.
The reverse direction is a separate decision, and it’s called DNAT (Destination NAT). Here the destination address of a packet arriving from outside is changed, i.e. which inside server an incoming request from the outside world will reach.
| Property | SNAT | DNAT |
|---|---|---|
| Direction | Inside to outside | Outside to inside |
| Field changed | Source address and port | Destination address and port |
| Who starts the entry | The inside device, automatically as the packet leaves | No one; the rule must be written by hand |
| What happens without it | The answer can’t return | The connection dies at the edge, never reaches inside |
| What the far side sees | Not you, the gateway’s address | The real client address, because the source doesn’t change |
| Everyday name | Getting on the internet | Port forwarding |
The most important difference between the two isn’t technical, it’s conceptual: SNAT and DNAT are independent on the same device and can point at different addresses. The passport you show on the way out and the address used to reach you don’t have to be the same. That sentence sounds theoretical, but in the field it’s a direct design tool: outbound can be sent through one line while inbound is answered on another, because the outbound direction is SNAT (a policy choice about where you connect to) and the inbound direction is DNAT (a separate policy choice). Binding both to the same address is only the default, not a requirement.
What Is Port Forwarding? How a NAT Rule Is Written
What’s called port forwarding in everyday language is the most common form of DNAT: traffic arriving from outside on a specific port is delivered to a specific device and port inside.
iptables -t nat -A PREROUTING -d 192.168.1.15 -p tcp --dport 8080 \
-j DNAT --to-destination 10.10.1.2:8080The rule reads: take TCP traffic arriving from outside on port 8080 of address 192.168.1.15, and make its destination 10.10.1.2:8080. At the border it’s an instruction handed to the officer at the desk: “send the visitor arriving for this number to that apartment.”
The asymmetry here may be the article’s most practical sentence. You don’t need to write a rule for outbound; you must write one for inbound. On the way out the entry is created on its own as the packet leaves; on the way in there’s no entry to be created, because no one inside asked for anything. There’s no line in the officer’s ledger for that visitor, and no way to know who to call.
There’s also a less-discussed form of port forwarding: you’re not limited to sending a single port inside. If you have more than one public address, you can dedicate an entire address to a single service. This is the field equivalent of what the table above called static NAT, and it earns its keep during a migration: a whole public address pointed at one service keeps that service’s traffic separate from everything else, which isolates faults and keeps logs from mixing which flow belonged to whom.
What Are Double NAT and CGNAT?
Until now there was a single border gate. In real networks there are often two: the provider’s modem does one translation, the organization’s own firewall does another. Translating traffic twice in a row is called double NAT.
On the metaphor it gets easier: you’re passing through a transit country. At your own country’s gate you’re given a passport; at the transit country’s gate that passport isn’t enough either and you’re handed a second document. The person at your destination sees only the second document and has no idea who you are.
Lab: Putting Two Translations in a Row
To show this we added one more layer. Now there are three levels: the innermost network, the middle gateway, and the outer gateway. Each does its own translation.
# The middle layer does its own translation
ip netns exec mid iptables -t nat -A POSTROUTING -s 10.10.1.0/24 -o veth-mo -j MASQUERADE
# The outer layer translates once more
iptables -t nat -A POSTROUTING -s 10.30.0.0/24 -o ens33 -j MASQUERADEListening to the same connection at three points at once, you can see the address change twice:
LAYER 1, on the innermost network (not translated at all):
10.10.1.2.40000 > 192.168.1.10.389: Flags [S], seq 2596541764
LAYER 2, between the two gateways (translated once):
10.30.0.2.40000 > 192.168.1.10.389: Flags [S], seq 2596541764
LAYER 3, on the outside interface (translated twice):
192.168.1.15.40000 > 192.168.1.10.389: Flags [S], seq 2596541764All three lines share the same sequence number (seq 2596541764), so this is one packet seen at three places. The source address changed once at each layer.
- Inside deviceno translationsends the packet with this address10.10.1.2
- 1st gatewayfirst translationrewrites the source to10.30.0.2Its ledger holds only
10.10.1.2; it knows nothing of the outer gateway. - 2nd gatewaysecond translationrewrites the source to192.168.1.15Its ledger holds only
10.30.0.2; it has never heard of an address called10.10.1.2. - Target serversees no translationthe only address it sees192.168.1.15
And now there are two separate ledgers:
The middle gateway's ledger:
tcp ESTABLISHED src=10.10.1.2 dst=192.168.1.10 sport=40000 dport=389
src=192.168.1.10 dst=10.30.0.2 sport=389 dport=40000 [ASSURED]
The outer gateway's ledger:
tcp ESTABLISHED src=10.30.0.2 dst=192.168.1.10 sport=40000 dport=389
src=192.168.1.10 dst=192.168.1.15 sport=389 dport=40000 [ASSURED]Each ledger knows only its own layer. The outer gateway is unaware there’s an address called 10.10.1.2; the only inside address it knows is 10.30.0.2.
Why One Rule Isn’t Enough
This is exactly where double NAT produces its faults. Say we want to reach an inside server from outside: we opened a service listening on port 8080 on the innermost network and wrote a DNAT rule on only the outer gateway.
Trying from outside, from a real Windows machine on the network:
Test-NetConnection -ComputerName 192.168.1.15 -Port 8080PingSucceeded : True
TcpTestSucceeded : FalseThe symptom is exactly the sentence you hear in the field: the machine is up, ping answers, but the service can’t be reached. These two lines sitting side by side matters, because PingSucceeded : True often gets read as “the network works” and the diagnosis heads the wrong way.
The reason is that the outer gateway hands the packet to the middle gateway, but the middle gateway has no rule to receive it. When we write the rule on the second layer too:
ip netns exec mid iptables -t nat -A PREROUTING -d 10.30.0.2 -p tcp --dport 8080 \
-j DNAT --to-destination 10.10.1.2:8080PingSucceeded : True
TcpTestSucceeded : TrueLooking at the inbound connection’s ledgers is instructive too:
Outer gateway: src=192.168.1.224 dst=192.168.1.15 dport=8080
src=10.30.0.2 dst=192.168.1.224 sport=8080
Middle gateway: src=192.168.1.224 dst=10.30.0.2 dport=8080
src=10.10.1.2 dst=192.168.1.224 sport=8080The destination address changed twice (192.168.1.15, then 10.30.0.2, then 10.10.1.2), but the source address never changed: 192.168.1.224 is the same in both ledgers. So DNAT doesn’t hide the client. What hides it is SNAT, and because the two are independent, the inside server keeps seeing the real client address.
CGNAT: When You’re Not the One Translating
CGNAT (Carrier Grade NAT) is the form of double NAT that isn’t under your control. Because of IPv4 scarcity, the provider shares a single public address across hundreds of subscribers; when you look at your modem you don’t see a public address, you see a private one assigned to you.
Technically it’s the same setup we measured above, and its one difference is decisive: the second layer isn’t yours. When an inside server needs to be reached from outside, you can’t write the second rule you’d need, because you have no access to that device. If port forwarding doesn’t work despite being configured, and your line is CGNAT, the problem is not in your configuration.
The practical consequence: in any scenario that needs remote access, a camera system, a site-to-site VPN, or a branch link, whether the line carries a real public address is a question to ask at the contract stage. Solving it afterward usually means a change of plan.
Will IPv6 Put an End to NAT?
CGNAT isn’t a preference, it’s a symptom of scarcity: it exists because the provider has run out of IPv4 addresses to hand out. So the first question that comes up is: if it was born because addresses ran out, does it disappear when addresses don’t run out?
In terms of the reason address translation exists, the answer is yes. The IPv6 address space is wide enough to give every device its own public address; scarcity is no longer a problem, so the need to cram many devices behind one address goes away. IPv6 was designed for end-to-end addressability from the start.
But there’s a second job attributed to NAT in the field, and that’s where the real confusion is. Many people think of NAT as a security layer: “inside devices aren’t visible from outside.” The mechanism we measured through this whole article shows why that’s a misreading. What stops an inbound connection isn’t NAT itself, it’s the absence of a line for that connection in the ledger. The protection was never a design, it was a side effect of translation.
In IPv6 that side effect disappears, because there’s nothing to translate. This does not mean every device becomes reachable from outside: an address being public and that address being reachable are two different things. Stopping an inbound connection is now plainly the firewall’s job, done with a stateful rule set. Most home devices already do this by default; on the enterprise side it becomes a rule you have to write.
The border-gate equivalent is clean: in IPv6 everyone has their own passport, so there’s no need for a desk handing out shared ones. But the officer at the gate is still there, still deciding who comes in. Removing the desk isn’t opening the gate.
In practice the transition is partial and long. The vast majority of networks will carry both protocols for a while yet; as long as the IPv4 side is up, so is the NAT on it. So don’t expect NAT to be a topic that vanishes from software any time soon; the diagnostic order in this article holds as long as you have an IPv4 line.
NAT Diagnosis: Server Is Up but Unreachable From Outside
In the field, most NAT-caused faults arrive as a single sentence: “the server works, we get in from inside, we can’t from outside.” In the metaphor: a visitor came to the gate, the person they’re looking for is at their desk inside, but there’s no line in the officer’s ledger for that visitor. The officer isn’t hostile, they just don’t know who to call. Diagnosis therefore runs not from “is the server up” but from “is there a line for this connection in the ledger.”
Build the order backward along the path the packet takes, because the most expensive mistake is diving inside before looking at the outermost device.
- Is the service really listening? On the inside machine, confirm the port is in listening mode. If it isn’t, no NAT rule saves it.
- Is it reachable from inside, on the same network? If it is, the problem is the path, not the service.
- How many layers of translation are there? If the modem and firewall are separate devices, both are probably translating. Each layer needs its own rule.
- Does the edge device have a real public address? If the modem’s WAN address starts with
10.,100.64.or192.168., your line is CGNAT and reaching it from outside isn’t possible with this setup. - Is the rule written in the right direction? Writing an outbound rule (SNAT) and expecting inbound is the most common mix-up in the field.
Translation and permission are two different decisions, and neither alone lets a connection form: even after a packet passes translation, it can still be stopped by a permit rule. It’s worth running this order alongside a rule review on the firewall itself, because a packet that clears translation can still catch on the allow list.
Hairpin NAT: Reaching Your Own Public Address From Inside
There’s a special case, and it’s the one that most misleads diagnosis. An inside user tries to reach a server that’s also inside, but through its public address. The packet leaves the inside, goes to the edge device, and has to come back from there. That return trip is called hairpin NAT, or NAT loopback.
The problem on the mechanism side: the edge device has to translate this packet in both the destination and the source direction. If it only translates the destination, the inside server sends its answer straight to the client; the client, expecting the answer from the public address, rejects a packet from a source it doesn’t recognize. The connection fails to form with both ends up. Some devices don’t support this double translation at all; on those that do, the traffic passes needlessly through the edge.
The clean fix usually lives on the name-resolution side rather than the NAT side: return the internal address to the inside user. We cover the setup in the DNS guide and the Active Directory DNS guide. But the mechanism itself is worth seeing measured, because “works from outside, not from inside” is a symptom people spend real time misreading.
We Measured It
We built the case on the same one-machine setup, with one condition that matters: the client and the server sit on the same inside segment (10.10.1.0/24), the way a workstation and a server share a LAN in a real office. The gateway’s public address is 203.0.113.10, the inside server listens on 8080, and there is exactly one DNAT rule, the same port-forward we wrote earlier:
iptables -t nat -A PREROUTING -d 203.0.113.10 -p tcp --dport 8080 \
-j DNAT --to-destination 10.10.1.3:8080From outside (the control). A client on the outside network reaches 203.0.113.10:8080. It works, and the server sees the outside client’s real address:
TEST1 (WAN) HTTP=200On the server side, the first packet arrives from the outside client’s real address:
19:12:41.852873 IP 203.0.113.2.41452 > 10.10.1.3.8080: Flags [S], seq 3659343396, win 64240, options [...], length 0The reply routes back through the gateway (the client is on a different network), the gateway reverses the DNAT, and the answer arrives from 203.0.113.10 as the client expects. A single DNAT rule is enough for the outside.
From inside, with only that DNAT rule. Now an inside client on the same segment reaches the same 203.0.113.10:8080. It fails:
TEST2 (LAN, hairpin SNAT yok) HTTP=000(The result labels come from the lab script, which is in Turkish: yok means none, var means present.)
The capture on the server side says exactly why. The client’s SYN reaches the server after the DNAT, so the server sees the client’s real inside address 10.10.1.2; it then sends its SYN-ACK straight back to 10.10.1.2 over the local segment, never passing through the gateway. The client dialed 203.0.113.10, so an answer arriving from 10.10.1.3 is from a stranger, and it resets the connection at once:
19:12:42.966963 IP 10.10.1.2.44220 > 10.10.1.3.8080: Flags [S], seq 1475791315, win 64240, options [...], length 0
19:12:42.967008 IP 10.10.1.3.8080 > 10.10.1.2.44220: Flags [S.], seq 1546136757, ack 1475791316, win 65160, options [...], length 0
19:12:42.967017 IP 10.10.1.2.44220 > 10.10.1.3.8080: Flags [R], seq 1475791316, win 0, length 0The ledger shows the connection stuck, never confirmed, the return the gateway expected having never come back through it:
tcp 6 115 SYN_SENT src=10.10.1.2 dst=203.0.113.10 sport=44220 dport=8080 [UNREPLIED] src=10.10.1.3 dst=10.10.1.2 sport=8080 dport=44220 mark=0 use=1The fix on the NAT side. We add the second, source-side translation, so the reflected connection also gets its source rewritten to the gateway:
iptables -t nat -A POSTROUTING -s 10.10.1.0/24 -d 10.10.1.3 -p tcp --dport 8080 \
-j SNAT --to-source 10.10.1.1Now the same inside client reaches 203.0.113.10:8080 and it works. The server no longer sees the client; it sees the gateway 10.10.1.1, so it sends its answer to the gateway, which unwinds both translations and hands the client an answer from the address it dialed:
TEST3 (LAN, hairpin SNAT var) HTTP=200Now the first packet the server sees comes from the gateway:
19:12:47.562131 IP 10.10.1.1.52028 > 10.10.1.3.8080: Flags [S], seq 809448235, win 64240, options [...], length 0tcp 6 119 TIME_WAIT src=10.10.1.2 dst=203.0.113.10 sport=52028 dport=8080 src=10.10.1.3 dst=10.10.1.1 sport=8080 dport=52028 [ASSURED] mark=0 use=1Three tests, one DNAT rule the whole time: it works from the outside, fails from the inside, and works from the inside once the source-side translation is added. That is why “works from outside, not from inside” points at hairpin, not at a broken rule. The NAT-side fix above is real, but on most networks the cleaner answer is the DNS one: hand inside users the internal address, and the packet never has to reflect at all. When a user says “it works from outside but not inside,” the first thing to check is which address they’re being handed.
When the External Identity Changes
Every example so far assumed the public address was fixed. It isn’t really yours, though: it belongs to the edge device. Switch providers, or replace the firewall, and the identity the outside world knows you by changes with it, for the reason this article opened with. The outside recognizes you not by your inside address but by the one the edge hands out, so when the edge device changes, that identity changes too.
The consequence catches people out during a migration: a technically flawless cutover can look like a mass outage from the user side if everything that depends on the old address, VPN clients, partner allowlists, DNS records pointing at it, isn’t updated ahead of time. Move the border gate to another city and the passport numbers change with it; if no one is told in advance, no one gets back in. The external identity is something to manage, not just something you configure once.
Conclusion
Summing NAT up as “the thing that changes the address” takes the easy half. What the measurements showed is that the real mechanism is the ledger: for each connection the translating device writes two tuples, one defining the outbound and one the expected return, and it compares every packet arriving from outside against that second tuple. When we deleted the rule, packets still left; what couldn’t leave was the answer.
The three points that earn their keep in practice:
- Outbound and inbound are separate decisions. SNAT and DNAT can point at different addresses on the same device; splitting outbound onto one line and an inbound service onto another is a design tool, not an accident.
- However many layers the translation is on, the inbound rule has to be on that many layers. Outbound chains itself; inbound doesn’t. Nearly every double-NAT fault comes down to that one sentence, and hairpin is the same lesson turned inward: one rule that’s enough from outside isn’t enough from inside.
- The external identity is a managed asset. It changes when the edge device changes, and everyone who depends on it needs to know in advance.
And let’s write where the metaphor lies, because that’s where the mechanism itself sits. A real passport is yours and returns with you; NAT’s passport stays at the gate, and the outside never learns your name. More than that, the officer’s ledger isn’t permanent. A line that goes quiet for a while is deleted, and that connection drops, with both ends up and no one having done anything.
NAT doesn’t stand alone here: what an address is, how a port identifies a connection, and how a name is turned into an address are links in the same chain. Start with what a port is, or how DNS resolves a name.
Questions about NAT
This article is adapted from a guide the author first published in Turkish on sercebilisim.com: NAT Nedir? Ağ Adresi Çevirisi Nasıl Çalışır?
Written by
İlker PehlivanNetwork and systems engineer, founder of Serçe Bilişim
I run the networks and servers that other people's work depends on. Before founding my own consultancy I administered the backbone network, firewalls and core systems of a large multi-site organisation with thousands of users. I write about the things that actually broke.
Related articles
What Is a Port? TCP vs UDP and Connection Faults
What a port number does, how TCP and UDP differ, how to check open ports, and, when a connection won't form, which side the fault is on and how to prove it.
What Is a VLAN? 802.1Q Tags, Access vs. Trunk Ports
A VLAN splits one switch into separate networks using a 4-byte tag. How 802.1Q works, access vs. trunk ports, and native VLAN risk, with real packet captures.
What Is ARP? ARP Table States, Broadcast Domains, Spoofing
ARP maps an IP address to a MAC address on the local network. Cache states, where the broadcast domain ends, and ARP spoofing, shown with real packet captures.
What Is DNS and How Does It Work? Records and TTL
What DNS is and how it works: the root, TLD and authoritative chain; A, CNAME, MX, TXT records; nameserver delegation, TTL and caching, and diagnosing faults with nslookup.
On this page
Part of this guide
Network Segmentation Guide: VLANs, Layers, and Access