The two-tier model
When you check your IP from your laptop ("what's my IP"), you usually see one number from whatismyip.com and a different one from ifconfig. They look like:
whatismyip.com: 93.184.216.34 ← what the rest of the internet sees
ifconfig: 192.168.1.42 ← what your laptop is on the LAN
These are different layers, both real, both yours.
What public IPs are
A public IP is globally unique on the internet. There are ~4 billion IPv4 addresses; with NAT (see below) we make do with that. IPv6 has ~10^38 — effectively infinite — so future-public-IP isn't a constraint.
The internet's routing tables agree on which router owns which public IP. Send a packet to 93.184.216.34 and the global routing system delivers it.
What private IPs are
Private IPs are addresses reserved for use inside a network — they're not unique globally. The reserved ranges:
10.0.0.0/8— corporate / large LANs172.16.0.0/12— uncommon but valid192.168.0.0/16— home routers default herefc00::/7— IPv6 unique-local addresses
Every home Wi-Fi network has its own 192.168.1.x space. There are millions of 192.168.1.42s in the world. They don't conflict because the internet never sees them — the public IPv4 routing system literally drops packets headed to 192.168.x outside the LAN that owns it.
NAT: how the layers connect
Your home router has both: a public IP on the WAN side (assigned by your ISP), and a private IP on the LAN side. When your laptop sends a packet to 93.184.216.34, the router rewrites the source address from 192.168.1.42 to 93.184.216.1 (its public IP) and remembers the mapping. When the response comes back, it rewrites the destination from 93.184.216.1 back to 192.168.1.42.
This is Network Address Translation (NAT). It's why dozens of devices on your home Wi-Fi share a single public IP.
The catch: NAT is one-way by default. Outbound connections work; inbound connections are dropped at the router because the router has no entry telling it which laptop the packet is for.
Why this matters for "expose localhost"
You want a webhook from Stripe to reach your dev server. Stripe sends to your public IP. Your router has no NAT mapping for the inbound — it drops the packet, never reaches your laptop.
Three classical fixes:
Port forwarding
Open a port on the router's admin page. Port 8080 → 192.168.1.42:8080. Now inbound on that public IP / port reaches your laptop.
Pain points:
- Most ISPs use carrier-grade NAT (CGNAT) — your "public IP" is itself private from the global internet's perspective. Port forwarding does nothing.
- Your public IP changes whenever the ISP feels like it. Dynamic DNS lags.
- Cellular and café Wi-Fi: you don't admin the router.
IPv6
If you have a real IPv6 address (most networks do in 2026), every device on your LAN has its own globally routable IPv6. No NAT, no port forwarding required. Trade-off: many residential firewalls still block inbound IPv6 by default; many webhook providers and OAuth IDPs still don't speak IPv6 in 2026.
Reverse tunnel
Run an agent on your laptop that holds an outbound connection to a public-IP server. Inbound HTTP arrives at the public server, gets forwarded over the tunnel, hits your laptop. The public-IP-side complexity is someone else's problem.
$ lrok http 3000
Forwarding https://violet-mole.lrok.io → http://127.0.0.1:3000
TL;DR mental model
- Public IP = your address on the internet (or shared via NAT).
- Private IP = your address on the local network. Doesn't route on the internet.
- NAT: the router's translation between the two.
- "Expose localhost" = make a private IP service reachable from the public internet.