Securely Exposing a Private Staging App to BrowserStack Automate
When your staging app must stay private, BrowserStack Local tunnels let cloud browsers reach it without exposing it to the internet. This guide compares public URLs and local tunnels, explains trade‑offs, and shows a concrete, fail‑fast implementation using a unique localIdentifier per CI shard.
09 Sept 2025, 05:26 UTC

Decision Overview
When running cross‑browser tests against a staging environment that must not be internet‑exposed, the two viable paths are:
- Public URL – expose the staging host via a public DNS record.
- BrowserStack Local Tunnel – run a BrowserStack‑managed tunnel that forwards traffic from the cloud to the private network.
In most CI pipelines the BrowserStack Local Tunnel is the preferred choice because it keeps the staging host isolated, preserves authentication and IP‑whitelisting, and keeps secrets out of the repository.
Constraints Shaping the Decision
- Staging must remain behind a firewall; no public DNS or public IP.
- CI runs tests in parallel shards; each shard needs its own tunnel instance.
- Tests should fail fast if the tunnel cannot be established.
- BrowserStack access key must be injected via the CI secret store, never committed.
- Plan limits:
browserstack.localcapability is supported on all plans, but concurrency limits may affect how many tunnels you can run simultaneously.
Option Comparison
| Option | Setup | Parallel‑Safe | Exposure | Typical Cost |
|---|---|---|---|---|
| Public Staging URL | DNS record + firewall rule | Yes – no per‑job state | Internet‑facing | None (unless you add a CDN) |
| Local Binary per Job | Start BrowserStackLocal binary in background; wait for readiness | Yes – use a unique localIdentifier | None – traffic stays inside VPN | None (free local binary) |
| SDK‑Managed Tunnel | Framework starts/stops tunnel automatically via language binding | Partial – may share a tunnel across shards | None | None (free) |
Named Tunnel + localIdentifier | Start binary once per job, reuse across tests with same ID | Yes – isolates shards by ID | None | None |
Trade‑Off Highlights
- Security – Local tunnels keep the staging app hidden from the public internet, preserving auth and IP filters.
- Reliability – A single tunnel instance is a single point of failure; if it dies, all tests in that shard fail.
- Latency – Traffic routes through BrowserStack’s data center; add a few milliseconds per request.
- Bandwidth – All test traffic counts against your plan’s outbound limits; monitor usage.
- Complexity – SDK‑managed tunnels reduce boilerplate but can start after the first test, hiding startup failures.
- Parallelism – Using a unique
localIdentifierper shard guarantees that two shards don’t share a tunnel and accidentally hit each other’s hosts.
Concrete Implementation: Local Binary + Unique Identifier
Prerequisites
- BrowserStack access key stored as CI env var
BROWSERSTACK_ACCESS_KEY. - CI job runs on a machine with outbound internet access (to reach BrowserStack).
- Staging host reachable from the CI machine via VPN or bastion; no public DNS.
Step 1 – Start the Tunnel
# In your CI job shell (e.g., GitHub Actions, GitLab CI, Azure Pipelines)
# 1. Generate a unique identifier for this job shard
export LOCAL_ID=$(uuidgen) # or any unique string per parallel job
# 2. Start BrowserStackLocal in the background
# - The binary must be on the PATH or specify the full path
# - Pass the access key and the localIdentifier
# - Redirect logs to a file for later inspection
browserstack-local \
--key $BROWSERSTACK_ACCESS_KEY \
--local-identifier $LOCAL_ID \
--logfile /tmp/bslocal.log &
# 3. Wait for the tunnel to be ready (poll the log or use the --wait flag)
# BrowserStackLocal prints "Tunnel established" when ready.
# Example: wait until "Tunnel established" appears in the log
for i in {1..30}; do
if grep -q 'Tunnel established' /tmp/bslocal.log; then
echo 'Tunnel ready'
break
fi
sleep 1
if [ $i -eq 30 ]; then
echo 'Tunnel failed to start' >&2
exit 1
fi
done
Step 2 – Configure Selenium Capabilities
# Example in Java (Selenium 4 W3C caps)
import org.openqa.selenium.MutableCapabilities;
MutableCapabilities caps = new MutableCapabilities();
caps.setCapability("browserName", "chrome");
caps.setCapability("browserVersion", "latest");
caps.setCapability("os", "Windows");
caps.setCapability("osVersion", "10");
// BrowserStack specific
caps.setCapability("browserstack.local", true);
caps.setCapability("browserstack.localIdentifier", System.getenv("LOCAL_ID"));
// Optional: add any other caps (project, build, etc.)
Step 3 – Run a Smoke Test
# Minimal test: open internal URL and assert a known element
WebDriver driver = new RemoteWebDriver(
new URL("https://hub.browserstack.com/wd/hub"), caps);
// Internal staging URL – no public DNS
String internalUrl = "https://staging.internal.company.com/home";
driver.get(internalUrl);
// Verify that a page element is present
WebElement header = driver.findElement(By.cssSelector("h1"));
if (!header.getText().contains("Welcome")) {
throw new AssertionError("Smoke test failed – header missing");
}
// Clean up
driver.quit();
Step 4 – Terminate the Tunnel
# Kill the background process
pkill -f 'browserstack-local'
Validation Checklist
- Smoke Test Pass – The test above should complete within a few seconds; otherwise, the tunnel is not ready.
- Automate Dashboard – The session should show the internal URL rendered in the browser. Verify the page title or a unique element.
- Local Log – Check
/tmp/bslocal.logfor the lineTunnel establishedand no errors. - Parallel Isolation – Run two CI jobs in parallel with distinct
LOCAL_IDvalues; ensure each session only reaches its own staging host. - Firewall Rules – Confirm that the staging host has no public DNS entry and that only the CI machine (or VPN) can reach it.
Limitations & Mitigations
- Single Point of Failure – If the tunnel binary crashes, all tests in that shard fail. Mitigation: add a quick readiness check (see Step 3) and a short retry loop.
- Bandwidth & Cost – All traffic counts against your BrowserStack plan. Monitor the
Data Transfermetric in the dashboard. - Secret Management – Never hard‑code
BROWSERSTACK_ACCESS_KEY. Use the CI secret store and rotate keys regularly. - Plan Concurrency Limits – Some plans limit the number of simultaneous Local tunnels. Verify your plan’s limits before scaling.
- Non‑Standard Capabilities –
browserstack.localandbrowserstack.localIdentifierare BrowserStack‑specific; tests will not run on a generic Selenium grid.
When to Consider a Public URL
- Your staging environment already has a public DNS and is intentionally exposed for QA or demo purposes.
- Latency or bandwidth constraints make a tunnel impractical.
- Your CI plan does not support the required number of concurrent Local tunnels.
Even in these cases, keep authentication and IP whitelisting in place; a public URL does not automatically expose the app to the world.
Takeaway
For most secure CI pipelines, starting a BrowserStack Local binary with a unique localIdentifier per shard gives you the isolation and safety you need while keeping the staging host private. Use a quick smoke test to gate the rest of the suite and validate the tunnel’s health before proceeding.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.