Why this matters
You build a small web app and run it on your laptop. It works at http://localhost:3000 in your browser. You send the same link to a friend on another network, and nothing loads. You check your address on a "what is my IP" website and get a number that does not match the one your laptop shows in its network settings.
Both surprises come from one device between your laptop and the internet: the router in your home. This lesson explains how that router keeps track of connections, what a port forwarding rule does, and why your laptop stays hidden until you write one.
The earlier lesson "Clients, servers and IP" gives NAT one paragraph. This lesson works through it with numbers.
The idea: the router keeps a notebook
NAT stands for network address translation. The router changes the address on each packet as the packet passes through it, and it writes each change in a notebook so it can undo the change when the reply comes back. The notebook is called the NAT table.
Here is a concrete home. Ana's laptop has the private address 192.168.1.20, which works only inside her home. Her home has one public address, 203.0.113.7, which her internet provider gives to the router and which the rest of the internet sees. The addresses 203.0.113.x and 198.51.100.x are reserved for examples in documents, so no real server uses them. We will use them as stand-ins for the wider internet.
A port is a number from 0 to 65535 that picks which program on a computer receives the data. Lesson 01 covered ports. Here we need two of them: the port on Ana's side and the port on the server's side. Port 443 is the usual port for HTTPS, the secure web.
Ana's browser opens a connection. The laptop's program uses port 51514 on its side, and it talks to the web server 198.51.100.10 on port 443. The first packet leaves the laptop like this:
| Step | Where the packet is | From (source) | To (destination) |
|---|---|---|---|
| 1 | Leaving the laptop | 192.168.1.20:51514 | 198.51.100.10:443 |
| 2 | Leaving the router | 203.0.113.7:40000 | 198.51.100.10:443 |
| 3 | Reply arriving at the router | 198.51.100.10:443 | 203.0.113.7:40000 |
| 4 | Delivered to the laptop | 198.51.100.10:443 | 192.168.1.20:51514 |
The router changes step 1 because the server sends its reply to the source address of the packet it received. If the packet still said 192.168.1.20, the reply would go to an address that means nothing on the internet, and it would be lost. So the router replaces the source with its own public address and a public port number it picks, 40000. It keeps the real values in its notebook.
Here is a small model of that notebook, written in plain Python. It is not a real router, but it follows the same rules. The first example runs the four steps above.
Output:
Example 1: laptop 192.168.1.20 asks a web server, and the reply comes back
leaving the laptop -> 192.168.1.20:51514 to 198.51.100.10:443
leaving the router -> 203.0.113.7:40000 to 198.51.100.10:443
reply arriving at the router -> 198.51.100.10:443 to 203.0.113.7:40000
delivered to the laptop -> 198.51.100.10:443 to 192.168.1.20:51514
table: {('192.168.1.20', 51514, '198.51.100.10', 443): 40000}
Example 2: two laptops ask the same server, both from source port 51514
laptop A goes out as -> 203.0.113.7:40000 to 198.51.100.10:443
laptop B goes out as -> 203.0.113.7:40001 to 198.51.100.10:443
reply to public port 40000 -> 198.51.100.10:443 to 192.168.1.20:51514
reply to public port 40001 -> 198.51.100.10:443 to 192.168.1.21:51514
Example 3: a stranger 203.0.113.99 sends packets to the public address
stranger to port 40000 -> dropped
rule: public port 8080 goes to 192.168.1.20 port 8000
stranger to port 8080 -> 203.0.113.99:4000 to 192.168.1.20:8000
stranger to port 8081 -> droppedThe outbound method does the rewriting. The first time it sees a connection, it picks the next free public port and writes a row. Every later packet from the same connection reuses that row.
The inbound method is the gate. A packet from outside is delivered only if it matches a row exactly: same public port, and same outside address and port that the row recorded. If nothing matches, the method returns None, which means the packet is dropped. The router does not send an error back to the stranger. The packet simply disappears.
Real routers also remember whether a row is for TCP or UDP (the two protocols from lesson 01), and they delete rows that have been quiet for a while. The model leaves those details out because they do not change the main idea.
Two laptops, one public address
Now a harder case. Ana's partner, Ben, has a laptop at 192.168.1.21. Both laptops open a connection to the same server, 198.51.100.10:443, and both happen to use source port 51514. Source ports are chosen by each laptop on its own, so this can happen.
If the router only rewrote the address, the two replies would look identical when they arrive at 203.0.113.7. The router could not tell which laptop each reply belongs to. The public port number solves this. Laptop A's connection gets 40000 and Ben's gets 40001, so each reply arrives on a different number, and the notebook knows where each one goes.
In Example 2 above, laptop A already has a row from Example 1, so it keeps 40000. Laptop B is new, so it gets 40001. Each row is one connection, not one laptop. A single laptop with ten browser tabs open to the same site has ten rows.
The server never sees the notebook or the laptops' addresses. It sees only the public address and the port the router chose. That is why many laptops can share one address without knowing about each other.
Port forwarding: a fixed rule for incoming traffic
The notebook only has rows for connections that a laptop started. A stranger's first packet has no row, so the router drops it. This is the reason your laptop is hidden.
Port forwarding is a rule you write yourself in the router's settings. It says: packets that arrive at this public port go to this laptop address and this port. A rule is not a conversation. It does not expire when a connection ends. It stays until you delete it.
Example 3 above has one rule: public port 8080 goes to 192.168.1.20 on port 8000. Here is the same idea as a table you would see in a router's settings page:
| Public port | Send to laptop | Laptop port | Purpose |
|---|---|---|---|
| 8080 | 192.168.1.20 | 8000 | Ana's test website |
Worked example: showing a site from home
Ana wants to show a page to a friend. She starts a small web server on her laptop. Python includes one, and the command is python3 -m http.server 8000. The server must listen on all of the laptop's addresses for the router's rule to reach it. The option --bind 0.0.0.0 asks for that. This is the command she runs:
python3 -m http.server 8000 --bind 0.0.0.0The server prints a line when it starts. The line below comes from a test run with port 8791 on the machine used to write this lesson, so the number differs from Ana's:
Serving HTTP on 0.0.0.0 port 8791 (http://0.0.0.0:8791/) ...Next, Ana opens the router's settings and adds the rule from the table: public port 8080 to 192.168.1.20, port 8000. Her friend types http://203.0.113.7:8080 in a browser on a different network. The request goes to the router. The router finds the rule and sends the packet to Ana's laptop on port 8000. The server answers. The router rewrites the source of that reply so it appears to come from 203.0.113.7:8080, which is the address the friend typed.
A second example: the rule has to match the laptop
Rules point at a fixed address on your side. Ana's rule says 192.168.1.20. But her router gives addresses by lease, a time-limited assignment (lesson 01 explained DHCP, the system that hands them out). Suppose that after a restart her laptop gets 192.168.1.27 instead. The rule still points at 192.168.1.20. Now the router finds the rule and forwards the packet to a device that is not there, or that belongs to someone else. No device answers, and the friend waits and gives up.
Two fixes exist. Ana can ask the router for a reservation, which means the router always gives her laptop the same address. Or she can set a fixed address on the laptop itself. Either way, the rule and the laptop must agree.
A third example: one public port per rule
Ben also wants to show a site. He cannot use public port 8080 because Ana's rule already uses it. Each public port can point to only one laptop. Ben needs another public port, such as 8081, with his own rule that points to 192.168.1.21 on port 8000. A stranger who connects to 8081 reaches Ben's laptop, and a stranger who connects to 8080 reaches Ana's. Both rules sit side by side in the same table.
Why your laptop is not reachable by default
Put the two parts together. A packet from the internet reaches your laptop only if one of two things is true:
- The router has a notebook row for that exact connection, which means your laptop started it, or
- The router has a port forwarding rule for that public port.
For a laptop running a server with no rule, neither is true. The router drops the packet, and your laptop never sees it. This is a feature: strangers cannot reach your laptop just by finding its address. It is also a nuisance when you want to show your work.
What the bind address controls
The router is one gate. The laptop has its own gate, called the bind address. When a program starts a server, it tells the operating system which address to listen on:
127.0.0.1means "only programs on this laptop may connect". This is whatlocalhostmeans.0.0.0.0means "every address this laptop has", including the address on the home network.
A server bound to 127.0.0.1 refuses connections that arrive at the laptop's home-network address, even when the connection comes from the same laptop. The test below shows this on the machine used to write the lesson. It starts two servers, one on each bind address, and tries to connect to both through 127.0.0.1 and through the laptop's own LAN address.
import socket
probe = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
probe.connect(("198.51.100.10", 9))
my_ip = probe.getsockname()[0]
probe.close()
print("this computer's LAN address:", my_ip)
def try_connect(host, port):
c = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
c.settimeout(2)
try:
c.connect((host, port))
return "connected"
except OSError as e:
return f"{type(e).__name__}: {e}"
finally:
c.close()
def make_server(bind_ip, port):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((bind_ip, port))
s.listen(5)
return s
local = make_server("127.0.0.1", 8771)
everyone = make_server("0.0.0.0", 8772)
print("server A bound to 127.0.0.1:8771")
print(" connect to 127.0.0.1 ->", try_connect("127.0.0.1", 8771))
print(" connect to", my_ip, "->", try_connect(my_ip, 8771))
print("server B bound to 0.0.0.0:8772")
print(" connect to 127.0.0.1 ->", try_connect("127.0.0.1", 8772))
print(" connect to", my_ip, "->", try_connect(my_ip, 8772))
local.close()
everyone.close()Output:
this computer's LAN address: 172.27.204.37
server A bound to 127.0.0.1:8771
connect to 127.0.0.1 -> connected
connect to 172.27.204.37 -> ConnectionRefusedError: [Errno 111] Connection refused
server B bound to 0.0.0.0:8772
connect to 127.0.0.1 -> connected
connect to 172.27.204.37 -> connectedYour LAN address will differ. The pattern is what matters. A server for a forwarding rule must be bound to 0.0.0.0, or to the laptop's home-network address. A server bound to 127.0.0.1 cannot receive forwarded traffic, because forwarded packets arrive at the home-network address.
The bind address does not open your laptop to the internet by itself. Server B accepts connections on all addresses, but a packet from the internet still needs a notebook row or a forwarding rule at the router before it reaches the laptop.
Common mistakes
Binding to localhost, then wondering why the forward fails. The router forwards the packet correctly. The laptop's server refuses it. A Python client on the friend's side shows this error:
ConnectionRefusedError: [Errno 111] Connection refusedThe error number 111 is the Linux value, and other systems use other numbers. The meaning is the same everywhere: the packet arrived, and the device answered "no program listens on this port here". The fix is to start the server with a bind address of 0.0.0.0.
Forwarding to an address that changed. When the router has no rule, or the rule points at an address that no device uses, the packet is dropped without a reply. The friend's program waits until its timeout runs out. Python reports that as:
TimeoutError: timed outThe message above came from a test connection to an address that does not answer. A forwarding rule with a stale address produces the same silence. Refused means something answered. Timed out usually means nothing did.
Checking your public address and expecting your laptop's address. A website that shows "your IP" sees the router's public address, 203.0.113.7 in our example. Your laptop's address (192.168.1.20) never leaves your home. Forwarding rules use the router's public address, and the friend types that one.
Opening more than you need. A forwarding rule for port 22 (used by SSH, a remote login tool) lets anyone on the internet try to log in to that laptop. Forward only the port your project uses, and delete the rule when the project ends.
Setting the rule on the wrong router. A friend must reach the router that owns the public address they type. If the router's own status page shows a private address for its internet side, a second router sits in front of it. This arrangement is sometimes called double NAT. Then the rule must be on the first router, the one the internet can reach, or the packet is dropped there.
A debugging walkthrough
When a friend cannot reach your forwarded server, work through the three gates in order.
- Is the server running, and on which bind address? Start it, and check the startup line.
Serving HTTP on 127.0.0.1means only this laptop can connect.Serving HTTP on 0.0.0.0means every address is open to the home network. - Does the laptop answer on its home-network address? From the laptop, connect to its own LAN address, as the test above did. If that is refused, the server is not listening there. If it works, the laptop side is fine.
- Does the rule match the laptop and the port? Check the rule's public port, its laptop address, and its laptop port. Then check the laptop's current address, because the lease may have changed.
Your friend's error tells you which gate to check. Refused means the packet reached the laptop, so look at steps 1 and 2. Timed out means the packet did not reach the laptop, so look at step 3 and the router's rules.
Practice
Exercise 1. A new laptop, 192.168.1.30, opens a connection from its port 50000 to 198.51.100.20 on port 80. The notebook already has rows using public ports 40000 and 40001. What public port does the router choose, and what does the packet look like when it leaves the router?
Show the answer
The router picks the next free number, 40002. The packet leaving the router is 203.0.113.7:40002 to 198.51.100.20:80. The laptop's own address and port are replaced, and the new row records them.
Exercise 2. The notebook has the row from Example 1, which uses public port 40000 and belongs to 198.51.100.10:443. A packet arrives from 198.51.100.20:443 to public port 40000. Is it delivered?
Show the answer
No. The row for port 40000 accepts replies only from 198.51.100.10:443. The source of this packet is 198.51.100.20:443, which does not match, so the router drops the packet.
Exercise 3. The router has the rule "public port 2222 goes to 192.168.1.40 port 22". A friend connects to 203.0.113.7 on port 2222. Which device and port receive the packet? What happens if the friend connects to 203.0.113.7 on port 22 instead?
Show the answer
The laptop at 192.168.1.40 receives it, on port 22. Connecting to port 22 on the public address has no rule and no notebook row, so the router drops the packet.
Exercise 4. Your friend says "Connection refused" when connecting to your forwarded port. You checked that the rule points at the right laptop and port. Name two likely causes and say how to check each one.
Show the answer
First, the server may listen only on 127.0.0.1. Look at its startup line. If it says 127.0.0.1, restart it with a bind address of 0.0.0.0. Second, the rule may name a laptop port where no program runs, or the server may have stopped. Check that the server is running and that its port matches the rule's laptop port. Refused means the packet arrived at the laptop, so the router rule is probably correct.
Exercise 5. On Monday, Ana's rule 8080 to 192.168.1.20 port 8000 works. On Wednesday her friend gets a timeout. Ana finds that her laptop's address is now 192.168.1.27 after a restart. Why does the timeout happen, and what are two fixes?
Show the answer
The rule still points at 192.168.1.20. No device has that address now, so the router forwards packets to nothing and drops them without a reply, and the friend waits until the timeout. Fixes: ask the router for a reservation that always gives this laptop the same address, or set a fixed address on the laptop. Then update the rule if the address changed.
Summary
- The NAT table is the router's notebook. Each row records one connection that a device inside the home started.
- The router rewrites the source address and port of each outgoing packet, and it uses the notebook to deliver each reply to the right device.
- An incoming packet is delivered only if it matches a notebook row or a port forwarding rule. Otherwise the router drops it without a reply.
- Port forwarding is a fixed rule. It works only while the target laptop keeps the address the rule names.
- A server that should receive forwarded traffic must listen on
0.0.0.0, or on the laptop's home-network address. A server bound to127.0.0.1accepts only connections from the same laptop. - "Connection refused" means the packet reached a device that has no program on that port. "Timed out" usually means nothing answered, which points at the router or at a rule with a wrong address.