BrowserStack Local: Testing Localhost on Real Browsers
BrowserStack Local uses an outbound SSH tunnel to let cloud browsers reach your localhost. Here’s how it works, how to verify it, and where it breaks.
22 Jun 2026, 12:34 UTC

You've got a feature branch running on localhost:3000, and before merging you want to see it in Safari on a real iPhone and in an old Edge build. The obvious options are all bad: deploy to a staging environment (slow, and now your half‑finished branch is visible to everyone), or maintain a device lab (expensive, and it still doesn’t cover every browser version). BrowserStack Local exists to close that gap — it lets cloud‑hosted real browsers reach a server that only exists on your machine.
The problem it solves: private code, public browsers
BrowserStack’s cloud browsers live on BrowserStack’s infrastructure. By default they can only reach public URLs. Your dev server, a VPN‑only staging box, or a container on your CI runner are invisible to them. BrowserStack Local bridges this with an outbound SSH tunnel: a small binary on your machine initiates a connection out to BrowserStack and keeps it open. Because the connection is outbound, you don’t need to open inbound firewall ports or touch NAT rules — it works from a laptop on hotel Wi‑Fi.
When a remote test session is flagged as local, traffic from the cloud browser to localhost (or whatever host you map) is routed back through that tunnel to your machine. The browser thinks it’s talking to a local server; it’s actually talking to yours.
Setting it up: one binary, one key
Download the BrowserStackLocal binary for your OS, then start it from a terminal on the machine hosting your dev server. You need your Automate access key (found in your BrowserStack account settings) and permission to run executables locally:
./BrowserStackLocal --key YOUR_ACCESS_KEY
You should see a message like “You can now access your local server(s) in our remote browser” — that’s the tunnel handshake completing. Then, in your test script, mark the session as local. A minimal Selenium example in Python:
options = webdriver.ChromeOptions()
options.set_capability("bstack:options", {
"userName": "YOUR_USERNAME",
"accessKey": "YOUR_ACCESS_KEY",
"local": "true"
})
driver = webdriver.Remote(
command_executor="https://hub-cloud.browserstack.com/wd/hub",
options=options
)
driver.get("http://localhost:3000")
# ... your assertions ...
driver.quit()
The local: "true" capability is the entire contract. Without it, the cloud browser tries to resolve localhost on its own machine and your test fails with a connection error that looks deceptively like an app bug.
Verifying the tunnel instead of trusting it
Don’t assume the tunnel is up because the binary printed a success line twenty minutes ago. Two cheap checks:
- Dashboard: open the BrowserStack dashboard and look at the Local/tunnels indicator — active tunnels are listed there.
- REST API: query the tunnels endpoint with your credentials, e.g.
curl -u "USER:KEY" https://api.browserstack.com/automate/tunnels.json. An empty array means your tests are about to fail for infrastructure reasons, not code reasons.
In CI, a common pattern is to start the binary with a named identifier (--local-identifier my-ci-run), poll until the binary reports it’s connected, run the suite, then kill the process. The identifier matters once you have multiple jobs sharing one account — it ties a session to a specific tunnel.
Trade‑offs worth knowing before you commit
The tunnel is only as stable as the machine hosting it. If your laptop sleeps, the tunnel drops, and every in‑flight test fails with network errors. On CI runners, make sure the process is supervised or at least restarted on failure. There’s also latency: every request from the cloud browser to your app round‑trips through the tunnel, so a page that loads in 200 ms locally may take noticeably longer in the remote session. Don’t use remote‑session timings as performance data.
Account limits matter too. Lower‑tier plans restrict how many concurrent tunnels you can run, so parallel CI jobs may need to share one tunnel (use a single identifier and pass it to all sessions) rather than each spinning up their own. And remember the tunnel exposes whatever your machine can reach — if your dev box can see internal services, so can the remote browser for the life of the tunnel. Run it with the same care you’d give any port‑forwarding tool.
Where to go from here
Start with one test: run your existing suite against your local dev server with local: "true" and confirm it passes. Then wire the binary into your CI pipeline with a named identifier and an API‑based health check before the suite starts. That gives you real‑browser coverage of unmerged code — which is the entire point — without turning your network team into a dependency.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.