BrowserStack Local Tunnel Won't Connect: A Diagnostic Order of Operations
A diagnostic order of operations for BrowserStack Local tunnels that start but never route: cheap egress and proxy checks first, credential and identifier collisions second, escalation criteria last.
02 Nov 2025, 18:41 UTC

The condition: the tunnel process is up, but remote sessions still can't reach localhost
BrowserStack Local Testing works by running a small binary (bs-local) on your machine or CI runner. That binary opens an outbound connection to BrowserStack, and remote browsers are then routed back through it to your localhost or private-network URLs. The failure mode that wastes the most time is the split one: the process starts and stays running, but the remote session still reports that Local Testing is not enabled, or a test navigating to http://localhost:3000 fails with a connection-refused style error.
Treat "the binary is running" and "the tunnel is usable" as two separate facts. Most debugging time is lost by restarting the binary when the real problem is egress, proxy configuration, or a duplicate tunnel identifier.
Cause map: match the symptom before changing configuration
The table below maps observable symptoms to the cause that most often produces them. The "first check" column is deliberately cheap — run it before touching CI secrets or firewall rules.
| Symptom | Likely cause | First check |
|---|---|---|
| Binary exits immediately or logs an authentication error | Missing, wrong, or rotated access key | Compare the key in the CI secret store with a freshly generated key in the dashboard |
| Binary stays up but never reports a successful connection | Outbound access to the tunnel endpoint blocked by firewall, EDR, or DNS | curl -v https://bs-local.com from the same host |
| Connection attempt hangs, then times out | Corporate proxy required but not declared to the binary | Inspect HTTP_PROXY / HTTPS_PROXY and whether the binary was told about them |
| One pipeline job works, a parallel job breaks the first | Two tunnels sharing the same --local-identifier | Make the identifier unique per job |
| Tunnel connects, then drops repeatedly | Binary version drift against the current API | ./bs-local --version and compare with the current release |
Ordered checks
Run these on the same host or CI runner that launches the tunnel. None require elevated privileges; the only permission-sensitive step is modifying firewall or EDR allowlists, which is covered later.
- Confirm the binary runs and note its version. Run
./bs-local --versionin the directory where the binary lives. Compare the result against the current release listed in BrowserStack's documentation rather than against memory — the version numbering changes and old binaries can connect but behave inconsistently. - Confirm the flags you pass actually exist in that version. Run
./bs-local --helpand check each flag you use in CI. Flag names and defaults have changed across releases, so a flag that silently does nothing is a real possibility. - Start a tunnel with a unique identifier and verbose logging. Run this on the runner:
Expect a log line indicating the tunnel connected. The exact wording differs between versions, so read the log for the absence of reconnect warnings over the first 30 seconds rather than matching a specific string../bs-local --key "$BROWSERSTACK_ACCESS_KEY" \ --local-identifier "job-$(date +%s)" \ --verbose - Test egress independently of BrowserStack configuration. From the same host, run
curl -v https://bs-local.com. A completed TLS handshake proves DNS and outbound 443 work. If this fails, the problem is your network path, not your test capabilities — stop debugging the binary. - Check proxy handling explicitly. Many CI images set
HTTP_PROXYandHTTPS_PROXYfor package managers, but the tunnel binary does not necessarily honour them. If your network requires a proxy, pass it explicitly with the proxy host and port flags shown in--help, and supply credentials if the proxy demands them. - Rule out identifier collisions. Only one tunnel should run per
--local-identifier. In parallel pipelines, derive the identifier from the job ID or a timestamp. Note that plan limits on concurrent tunnels vary — free and trial accounts are typically restricted to a single tunnel, so verify your plan's limit before assuming a code bug. - Probe the tunnel health endpoint. A commonly referenced check is requesting the tunnel health URL from the runner and expecting an HTTP 200. Treat the exact host, port, and response body as version-dependent and confirm them in current documentation before wiring this into a pipeline gate.
Fixes tied to each finding
- Authentication failure: regenerate the access key in the dashboard, update the CI secret, and re-run. If key rotation requires admin approval in an SSO-managed account, start that request early — it is usually the longest lead-time item.
- Blocked egress: allowlist the tunnel endpoint on port 443 and allowlist the binary itself in your EDR or antivirus product. Endpoint agents that block unknown executables from making outbound connections are a frequent cause of a tunnel that starts and then goes silent.
- Undeclared proxy: add the proxy host, port, and credentials to the tunnel command, and keep them in CI secrets rather than in the pipeline file.
- Identifier collision: generate a per-job identifier, and serialise jobs if your plan's concurrency limit is one.
- Version drift: pin the binary version in your CI cache, or install it through the npm package with a locked version, instead of letting it auto-update mid-pipeline.
Platform and pipeline traps
- macOS quarantine: on macOS 10.15 and later, a downloaded binary may be quarantined by Gatekeeper. Removing the quarantine attribute before first execution is a standard workaround; confirm the exact attribute name on your OS version.
- Auto-update in CI: any flag that forces the binary to update itself can invalidate a pinned version and produce non-reproducible failures. Disable it in pipelines and upgrade deliberately.
- IPv6-only networks: binding behaviour can differ when only IPv6 is available. Verify with
curl -6and check--helpfor the binding-related flags in your version. - Protocol limits: Local Testing tunnels TCP traffic. Tests that depend on UDP — WebRTC, some media streaming — are not a good fit and generally need real devices instead.
When to escalate
Open a support ticket rather than continuing to iterate locally when any of these hold:
- The health endpoint responds successfully but test sessions still report that Local Testing is not enabled.
- The tunnel log shows a reconnect loop lasting several minutes despite a stable network path.
- You see a persistent authorization failure against the tunnel endpoint that survives a key rotation, which can indicate an account-level restriction.
- Key rotation is gated behind an enterprise SSO or SAML approval process you cannot complete yourself.
Verifying the fix, and what this guide does not cover
Verification has two halves. First, confirm the tunnel itself: the process logs a successful connection and shows no reconnect warnings shortly after start. Second, confirm the routing end to end: run a remote session with the local-testing capability enabled (in most bindings this is a browserstack.local capability set to true) and navigate to a local URL. If the tunnel is healthy but the session still cannot reach localhost, the capability is the next thing to check, not the network.
Marked for review before you rely on it: the health-endpoint host, port, and response body; the exact log strings emitted by current binary versions; the concurrency limit attached to your specific plan; and the IPv6 binding flags. These details change between releases and were not verified against live documentation for this draft. Confirm each against bs-local --help and the current BrowserStack documentation, and have a human review any CI change that depends on them.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.