Skip to main content
The CLI is designed to be the front door for scripts, not just terminals. Two patterns cover almost every case.
Will this work for your target? These recipes work for APIs, internal services, and sites that don’t aggressively block by ASN. If your target uses Cloudflare Bot Management, DataDome, or similar, the datacenter exit IP will get blocked at the edge regardless of region or rotation speed - no proxy product fixes that without residential IPs, which we don’t offer. See the introduction for the full caveat.

Pattern A: wrap the command

The wrapper form auto-injects HTTPS_PROXY / HTTP_PROXY / ALL_PROXY into the child’s environment, and tears the tunnel down when the child exits. The child’s exit code propagates to tunnelbyte.
tunnelbyte fsn -- ./scrape.py
tunnelbyte ash -- curl -sS https://example.com
tunnelbyte sin -- npm run e2e
Exit code semantics are documented in tunnelbyte exit codes.

Pattern B: long-running tunnel, multiple commands

Bring up the tunnel in the background, set the proxy env yourself, run anything you want against it, tear down when done. Useful when several commands share one session (cheaper, since the per-second meter doesn’t restart).
tunnelbyte fsn > /tmp/tb.out &
TUNNEL_PID=$!
trap 'kill $TUNNEL_PID' EXIT

# Wait for the proxy line and capture the URL.
PROXY=$(grep -m1 -oE 'http://127\.0\.0\.1:[0-9]+' <(tail -F /tmp/tb.out))
export HTTPS_PROXY=$PROXY
export HTTP_PROXY=$PROXY

./step-1.sh
./step-2.sh
python -m pytest tests/integration
The port is OS-allocated and changes every run - parse the proxy: line out of stdout (as above) instead of hardcoding a value.

GitHub Actions

Anonymous tier is enough for a smoke test; for anything real, set a TUNNELBYTE_TOKEN repo secret (paste the value from tunnelbyte token on a paid account).
name: scrape
on: [workflow_dispatch]
jobs:
  scrape:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install tunnelbyte
        run: curl -fsSL https://tunnelbyte.dev/install.sh | sh

      - name: Authenticate
        env:
          TUNNELBYTE_TOKEN: ${{ secrets.TUNNELBYTE_TOKEN }}
        run: |
          mkdir -p ~/.config/tunnelbyte
          printf '%s' "$TUNNELBYTE_TOKEN" > ~/.config/tunnelbyte/token

      - name: Scrape via Frankfurt exit
        run: tunnelbyte fsn -- python scrape.py
For matrixing regions:
strategy:
  matrix:
    region: [fsn, ash, sin]
steps:
  - run: tunnelbyte ${{ matrix.region }} -- python scrape.py --out ${{ matrix.region }}.json

Python

requests and httpx both read HTTPS_PROXY from the environment:
import os, requests
# Wrapper form: tunnelbyte fsn -- python script.py
r = requests.get("https://api.example.com/")
print(r.status_code, r.headers.get("X-Forwarded-For"))
If you need explicit control (the URL comes from HTTPS_PROXY set by the wrapper, or from the proxy: line of a backgrounded tunnel - don’t hardcode the port):
proxy = os.environ["HTTPS_PROXY"]
proxies = {"http": proxy, "https": proxy}
r = requests.get("https://api.example.com/", proxies=proxies)
aiohttp doesn’t honor HTTPS_PROXY by default; pass proxy= per request.

Node

fetch (Node 20+) honors HTTPS_PROXY only when run with --use-system-ca and NODE_OPTIONS set appropriately on older versions. The most portable path is undici’s ProxyAgent:
import { ProxyAgent, setGlobalDispatcher } from "undici";
const proxy = process.env.HTTPS_PROXY;
if (!proxy) throw new Error("run me inside `tunnelbyte <region> -- node ...`");
setGlobalDispatcher(new ProxyAgent(proxy));

const r = await fetch("https://api.example.com/");
console.log(r.status);

Cost-sensitive patterns

  • Reuse a single session across commands (Pattern B) instead of spawning one wrapper per request. Per-second metering + per-byte metering both stop the moment the session tears down, so the cheap path is “one long session, many requests.”
  • Choose the nearest region. Latency over the tunnel is mostly the round-trip from CI runner to exit node; pick the closest region to the runner, not the closest to the target. See tunnelbyte regions.
  • Watch the live counters with GET /v1/sessions/{id} (docs) - includes a live cost_*_millicents breakdown.

See also