Expose Localhost to the Internet in One Command
The problem
Your dev server runs on localhost:3000. Stripe, GitHub, your phone, and your client cannot reach it. You need a public HTTPS URL that forwards to your machine — right now, not after a deploy.
Most tunnel tools ask for an account, a config file, or a binary download before you get a URL. For a 30-minute webhook test, that friction kills momentum.
The fix (60 seconds)
npm run dev # Terminal 1 — your app
npx localtolink # Terminal 2 — public URL
You get output like:
Tunnel ready: https://k7m2x9p4q1.localtolink.com → localhost:3000
Copy that URL into your webhook dashboard, OAuth redirect URI, or share it with a client. No signup. No credit card.
When this workflow fits
| Scenario | One-command tunnel |
|---|---|
| Stripe or GitHub webhook test | Yes |
| OAuth callback during development | Yes |
| Share WIP with a client for an hour | Yes |
| Mobile responsive testing (QR in terminal) | Yes |
CI script needing a temporary URL (--json) | Yes |
| Production ingress with WAF | No — use proper infrastructure |
| Request inspection UI | No — use server logs or provider dashboards |
What you get on the free tier
LocalToLink is honest about limits:
- 2-hour sessions — enough for a webhook sprint or client call
- Random subdomain — restart gives a new URL; update external configs
- 1 GB bandwidth per session — fine for dev traffic
- 10 requests/second — fine for webhooks; not for load testing
- 1 tunnel per IP — one public URL at a time
These limits exist so the service stays predictable. They are not hidden caps on a shared server.
Step-by-step first run
- Start your app on a port (3000, 5173, 8080, etc.)
- Run
npx localtolink— auto-detects port if only one app runs - Copy the HTTPS URL from terminal output
- Register it wherever an external service needs to reach you
- Stop with Ctrl+C when done — tunnel closes immediately
Need a specific port:
npx localtolink --port 5173
Need machine-readable output for scripts:
npx localtolink --json
Common first-run issues
Port not detected — pass --port explicitly if multiple apps run.
Webhook returns 404 — your handler path must match what you registered (/webhook, not /webhooks).
OAuth redirect mismatch — the redirect URI must match character-for-character, including trailing slashes.
Session expired after 2 hours — restart the tunnel and update URLs in external dashboards.
Docker and containerized apps
If your app runs in Docker, the tunnel runs on the host and forwards to the published port:
docker run -p 3000:3000 my-app
npx localtolink --port 3000
Register https://YOUR-SUB.localtolink.com in external services — not localhost inside the container. A 502 Bad Gateway almost always means the tunnel port does not match the host mapping. See tunnel troubleshooting for Docker-specific diagnosis.
CI and scripted tunnels
Automation can start a tunnel and parse JSON output:
npx localtolink --port 3000 --json
The JSON payload includes the public URL for use in subsequent test steps. Sessions still respect the 2-hour limit and one-tunnel-per-IP policy — suitable for short integration tests, not long-running CI jobs.
What happens under the hood (briefly)
LocalToLink opens an outbound connection from your machine to the tunnel service. Incoming HTTPS requests on your assigned subdomain traverse that connection to your local port. No inbound firewall rules, no router configuration, no static IP. Your app sees ordinary HTTP requests as if they came from localhost — headers and body preserved.
That is why webhooks, OAuth callbacks, and API clients work without code changes: only the registered URL in external dashboards changes.
Choosing this workflow vs heavier setup
| You need… | One command fits? |
|---|---|
| First webhook delivery in 5 minutes | Yes |
| Client preview this afternoon | Yes |
| OAuth callback on HTTPS | Yes |
| Permanent staging URL for the team | No — deploy or named ingress |
| Custom domain on your zone | No |
| Visual request replay UI | No — use logs or inspection tooling |
Try npx localtolink on your current project before evaluating alternatives. If the 2-hour session and random subdomain work for your sprint, you are done. If not, you will know exactly which limit blocked you.
Honest limits
LocalToLink is a development tool, not production ingress:
- No custom domains on the free tier
- No request inspector or replay UI
- Single-region service — not for high-traffic production
- Random subdomain changes on restart (10-minute reconnect grace for brief disconnects)
Try it on your project. If the workflow fits, keep using it. If you need inspection or permanent URLs, use the right tool for that layer.
FAQ
Do I need an account?
No. Anonymous tunnels work immediately.
Is traffic encrypted?
Yes. Public URL is HTTPS; tunnel forwards to your local HTTP server.
Can I use WebSockets?
Yes — Vite HMR, Next.js Fast Refresh, and real-time apps work with proper config. See our Vite HMR guide.
What happens when I stop the tunnel?
The public URL stops working immediately. Restart for a new session.
Next steps
- How to expose localhost — full step-by-step guide
- Why webhooks fail on localhost — diagnose delivery issues
- CLI reference — flags, exit codes, and limits
Try it now
Start a tunnel from your project directory — no account required.
$ npx localtolink