Exposing localhost with a tunnel
Summary: a machine behind NAT has no public address, so nothing on the internet can call into it โ which blocks any webhook you want to develop against. A tunnel client makes an outbound connection to a provider that owns a public domain, and the provider forwards inbound requests back down it.
How a tunnel worksโ
caller โ https://xxxx.trycloudflare.com โ tunnel client โ localhost:8000
(provider owns the domain (running on (your server)
and its certificate) your machine)
- The connection is outbound. Your machine dials the provider, and traffic flows back down that existing connection โ so no port-forward, no inbound firewall rule, and NAT is irrelevant.
- You need no certificate. The provider owns the domain and terminates TLS at its edge; your app serves plain HTTP locally.
- Free URLs are ephemeral. They're regenerated on every restart, so anything registered against them (a webhook config) must be re-pointed each session. A named tunnel on your own domain gives a fixed URL.
Ports are global standards, not local choicesโ
Port numbers are assigned by IANA, the body that manages internet number standards. A port is a promise about what's listening, understood by every machine on the internet.
- 80 HTTP ยท 443 HTTPS ยท 22 SSH ยท 5432 PostgreSQL โ registered, universal.
- 8000 โ developer convention only. Nothing special; 8001 or 9000 behave identically.
- 443 is effectively unblockable. Blocking it breaks every website for the whole building, so corporate firewalls leave it open. Anything that needs to cross one aims for 443 โ which is also why "it works on 443" is no evidence that a protocol is allowed.
What tunnel clients actually use:
- cloudflared โ QUIC over UDP 7844, falling back to HTTP/2 over TCP 7844 if UDP is blocked. Same port, not 443; 443 is only used for some optional features. Corporate networks commonly block outbound UDP while allowing TCP, which is what the fallback is for (docs).
- ngrok โ outbound TLS on 443, like any HTTPS request. Its
:4040is the local inspector UI on127.0.0.1and never crosses the network (docs).
When a tunnel fails on a corporate network, ports are rarely the whole story. Check whether TLS interception is breaking the client's own connection (see certificate trust), and whether the provider's domain is category-blocked as an anonymiser โ both are common, and neither shows up as a port problem.
Where this came upโ
Shortcut webhooks needed to reach a FastAPI server on a laptop. ngrok didn't work on the agency network and cloudflared did; the cause was never established โ on ports alone ngrok should have passed, so TLS interception or domain blocking are the likely suspects.
Daily startup for that project:
docker-compose up -d
& "$env:TEMP\cloudflared.exe" tunnel --url http://localhost:8000
# copy the new URL into Shortcut โ Settings โ Webhooks โ https://xxxx.trycloudflare.com/webhook
curl https://xxxx.trycloudflare.com # expect {"detail":"Not Found"}
Not the same problem as SSL_VERIFYโ
| Certificate trust | Tunnel | |
|---|---|---|
| Direction | Outbound โ your app calling out | Inbound โ outside calling in |
| Problem | Proxy intercepts and re-signs your traffic | Machine has no public address |
| Whose certificate | The proxy's โ causes the problem | The provider's โ solves the problem |
| Fix | Trust the CA (SSL_VERIFY=false in dev only) | Run a tunnel client |