How to expose localhost to the internet

Your app runs on http://localhost:3000 and something out on the internet — a teammate, a phone, a webhook sender, an OAuth redirect — needs to reach it. Here are the free ways to do that, with the real trade-offs of each, and one important question to ask first: do you actually need to expose anything? (If the traffic is webhooks, the answer is usually no — jump to that.)

1. cloudflared quick tunnel — fastest zero-account option

$ cloudflared tunnel --url http://localhost:3000

Prints a public https://….trycloudflare.com URL that proxies to your port. Good: no account, no signup, solid infrastructure.
Trade-offs: the URL is random and changes every run, so anything configured with it (webhook providers, OAuth apps) breaks on restart. A stable named tunnel needs a Cloudflare account and your own domain.

2. ngrok — most polished, most walls

$ ngrok http 3000

Good: mature agent, request inspector, your URL is a stable auto-assigned dev domain on today's free plan.
Trade-offs: account + authtoken required before the first request; free monthly quotas (1 GB transfer, 20,000 HTTP requests); a browser-warning interstitial that automated callers trip over (ERR_NGROK_6024 and friends); capped concurrent agent sessions.

3. SSH reverse tunnels — nothing to install

$ ssh -R 80:localhost:3000 nokey@localhost.run

Services like localhost.run and serveo speak plain SSH, which you already have. Good: zero install, no account for basic use.
Trade-offs: random URLs per session and small-operator reliability; the same class of pain as localtunnel, whose password interstitial blocks programmatic senders outright (details).

4. Tailscale Funnel — stable URL, more setup

$ tailscale funnel 3000

Publishes the port on the public internet at your node's stable ….ts.net name. Good: URL never changes, real TLS, free tier is generous.
Trade-offs: you need Tailscale installed and an account; Funnel must be enabled for the tailnet; public listeners are limited to ports 443, 8443 and 10000. Best when you already run Tailscale anyway.

5. VS Code port forwarding — built into your editor

The Ports panel can make a forwarded port public on a devtunnels.ms URL. Good: zero extra tools if you live in VS Code.
Trade-offs: requires signing in with GitHub; visibility defaults to private (callers must authenticate — webhook senders can't); tied to your editor session, not scriptable.

The question to ask first: is it webhook traffic?

Every option above exposes a live port on your machine to the internet and dies (or breaks its URL) when the process stops. That's the right tool for demoing an interactive app to a human. But if the thing that needs to reach you is a webhook or callback — GitHub, Stripe, Shopify, Telegram, a payment provider — you can skip exposure entirely: give the provider a stable capture URL that's always up, and pull deliveries down to localhost yourself.

$ curl -s https://hookden.pages.dev/cli -o hookden && chmod +x hookden
$ ./hookden new                              # → stable /h/… URL, configure the provider once
$ ./hookden relay YOUR_BIN http://localhost:3000

Each delivery is re-delivered to your local server with the original method, sub-path, query, headers and a byte-identical body — HMAC signatures like GitHub's X-Hub-Signature-256 still verify. The public URL never rotates and keeps accepting deliveries while your laptop is asleep; the relay replays what you missed when you're back. Outbound polling only — nothing on your machine is reachable from the internet, so it works behind NAT, VPNs and corporate proxies where tunnels are blocked. Full walkthrough.

Honest scope: webhook-style HTTP traffic only — it can't serve a website or WebSocket app, delivery lags a couple of seconds behind the original, and bodies over 100 KB are truncated on relay (the CLI warns you).

Quick chooser

No signup needed. Or from your terminal: curl https://hookden.pages.dev/new

← All guides · Docs · Hookden vs webhook.site