Tunnel Not Working? Troubleshooting Guide
Start here
Before diving into tool-specific issues, confirm the basics:
- Your app is running on the port you expect
- The tunnel is running in a separate terminal
- You are using the HTTPS URL from current tunnel output — not yesterday's URL
- The path exists —
/webhookvs/webhooksmatters
curl -I https://YOUR-SUB.localtolink.com/
You should get a response from your app (200, 302, 404 from your app — not a tunnel error page).
Common errors
URL changed after restart
Every new tunnel session gets a random subdomain on the free tier. Update webhook URLs, OAuth redirect URIs, and shared links.
LocalToLink grace period: brief disconnects within 10 minutes may reconnect to the same URL. Full restart = new URL.
503 Tunnel Unavailable
Usually means the tunnel process is not running or the session expired (2-hour limit on free tier). Restart:
npx localtolink --port 3000
If you were using a shared public tunnel service elsewhere, 503 often indicates overloaded shared infrastructure — switch to a dedicated service for reliability-critical tests.
502 Bad Gateway
Your app is not reachable on the configured port:
- Verify
npm run devis running - Check
--portmatches your app - Docker users: tunnel must point to host port mapped from container
WebSocket / HMR broken
HTTP tunnels do not automatically proxy WebSockets correctly in all setups. For Vite:
// vite.config.js
export default {
server: {
hmr: { clientPort: 443, protocol: "wss" },
},
};
See Vite HMR through a tunnel and WebSocket guide.
Webhook URL changed every restart
External services cache nothing about your old subdomain. When you restart npx localtolink, you get a new random subdomain on the free tier. Every registered webhook, OAuth redirect, and shared client link must be updated.
Workflow that minimizes pain:
- Start app + tunnel at the beginning of a dev session
- Register URLs once in provider dashboards
- Use provider "resend" / "replay" features instead of restarting the tunnel unnecessarily
- Keep a sticky note or env var with the current tunnel URL during active work
LocalToLink offers a 10-minute reconnect grace — brief network blips may restore the same URL without a new subdomain. A full process restart always assigns a new subdomain.
Corporate Wi-Fi blocks tunnel
Some corporate networks block non-standard domains. Try mobile hotspot to confirm. Not fixable from the tunnel side.
Decision table
| Symptom | First check |
|---|---|
| Nothing loads | App running? Tunnel running? |
| Worked, then stopped | Session expired? Terminal closed? |
| Webhook 404 | Handler path correct? |
| WS disconnects | HMR config for your framework? |
| Slow responses | Normal tunnel latency; not localhost speed |
Switching tools mid-debug
Your app does not care which tunnel forwards traffic:
- Stop current tunnel
- Start
npx localtolink --port PORT - Update external URLs
- Retry
No application code changes.
Systematic diagnosis order
When something fails, walk this list in order — skipping steps causes false leads:
- Local first:
curl http://localhost:PORT/— if this fails, fix your app before blaming the tunnel - Tunnel process: terminal shows "Tunnel active" — if not, restart
npx localtolink - Public URL: copy from current terminal output, not chat history from yesterday
- Path: webhook at
/api/webhooks/stripeis not the same as/webhook - External config: Stripe/GitHub/OAuth dashboard matches step 3 exactly
- Session limits: 2-hour expiry, 1 GB bandwidth, 10 req/s — sustained load tests hit limits
Most reports of "tunnel broken" resolve at steps 1–3.
Docker-specific 502 diagnosis
Container networking causes more 502 errors than tunnel infrastructure:
# App in Docker, tunnel on host
docker run -p 3000:3000 my-app
npx localtolink --port 3000 # targets host port 3000
If the app binds only inside the container network without publishing -p, the host port has nothing listening. docker ps should show 0.0.0.0:3000->3000/tcp. For docker-compose, the tunnel --port must match the host side of the ports mapping.
Honest limits
LocalToLink enforces 2-hour sessions, 1 GB bandwidth, 10 req/s, 1 tunnel per IP. Hitting limits looks like failed requests or session termination — not random 503s from shared servers.
FAQ
Is the tunnel down globally?
Check your app locally first. Most "tunnel down" reports are expired sessions or stopped processes.
Can I get a fixed URL?
Not on free tier. Random subdomain each session.
Why does localtunnel behave differently?
Shared infrastructure tools have different failure modes. See our tunnel troubleshooting guide for generic advice.
How do I test if the tunnel reaches my app?
curl -v https://YOUR-SUB.localtolink.com/ from any machine. Compare response headers with curl http://localhost:PORT/.
Can I run two tunnels at once?
Free tier allows one tunnel per IP. Use different paths on one app or separate ports sequentially.
Latency expectations
Tunnel traffic adds one network hop between the client and your laptop. Typical added latency is tens to low hundreds of milliseconds — noticeable in demos but irrelevant for webhook delivery or OAuth redirects.
If responses feel sluggish:
- Confirm your local app responds quickly on localhost first
- Avoid running heavy background jobs during client calls
- Remember free tier rate limit is 10 req/s — burst traffic may queue
Next steps
Try it now
Start a tunnel from your project directory — no account required.
$ npx localtolink