Back to API docs

Hands-on tutorial

How to use random exit nodes

The goal is simple: send every request from a different mobile exit IP, and see for yourself that it really changes. curl only, copy-paste ready, about five minutes.

Written for a first integration. No proxy background needed — follow the steps in order. Every step states the expected result; if yours differs, jump to the troubleshooting table.

Step 0: two different “nodes”

This is where most first integrations stall — the address you connect to and the IP the target sees are two separate things.

TermWhat it isWhat you do
Entry node (gateway)The gateway server your client connects to. Its address is stable.Put it after curl -x and leave it alone.
Exit nodeThe phone that actually reaches the target site. It decides the IP and the region the target sees.Select it with -region-<region> inside the proxy username; randomise across regions for random exits.

In one line: the gateway address never changes. What changes is the username and how many connections you open.

Where the randomness comes from

The gateway picks an exit device at the moment a connection is established, so three things matter:

  1. 1

    The username carries no -session- and no -sessTime-. With them you get a sticky session, which deliberately pins you to one exit IP.

  2. 2

    Every request opens a new connection. On a reused keep-alive connection the request still travels the original tunnel, so the exit cannot change — this is the number one reason people report “my IP never rotates”.

  3. 3

    To randomise across regions, pick one from your available-region list yourself and build it into the username before each request.

Rotating means “scheduled again”, not “guaranteed different”. When a region has few online devices, drawing the same one twice in a row is expected.

Step 1: gather three things

All three live on the Proxy access page in the console — keep it open and copy from it.

Proxy account and password

ipx_demo8f2k / YOUR_PASSWORD

These are proxy credentials, not your sign-in email and password. Resetting the password in the console invalidates the old one immediately.

Gateway address

HTTP proxy.gaofengxt.cn:20001 · SOCKS5 proxy.gaofengxt.cn:20002

HTTP and SOCKS5 listen on different ports; do not paste the SOCKS5 port into an HTTP example. This tutorial uses the HTTP port.

Available exit regions

hangzhou / ningbo

A region slug is a city in the device pool. Requesting a region with no online device fails outright, so always take the list from the console.

Step 2: get one command working

Forget randomness for a moment and run the smallest working command. Replace the password with yours and paste it into a terminal:

Smallest working command
curl -x proxy.gaofengxt.cn:20001 \
  -U "ipx_demo8f2k-zone-mob-region-hangzhou:YOUR_PASSWORD" \
  https://ipinfo.io/ip

The username is three parts joined together

  • ipx_demo8f2kYour proxy account, copied from the console.
  • -zone-mob-region-A fixed infix — do not change or drop a single character.
  • hangzhouThe exit region code; change this part to change cities.

Expected result: one IP printed, for example 117.136.38.201. That is the address the target site sees right now. No output or an error? Skip to the troubleshooting table.

Step 3: prove the IP really rotates

Run the same command five times in a row and compare:

Five runs, watch the exit IP
for i in 1 2 3 4 5; do
  curl -s --http1.1 \
    -H 'Connection: close' -H 'Proxy-Connection: close' \
    -x proxy.gaofengxt.cn:20001 \
    -U "ipx_demo8f2k-zone-mob-region-hangzhou:YOUR_PASSWORD" \
    https://ipinfo.io/ip
  echo
done

Why --http1.1 and the two Connection: close headers: curl reuses connections and negotiates HTTP/2 when it can, and requests on one connection share one exit tunnel, so the IP never moves. Disabling reuse forces a fresh scheduling decision per request. The same applies in code — do not funnel everything through one long-lived connection.

If all five match: check the username for -session-, then check that both headers really made it into the request. If both are fine, that region most likely has a single online device right now.

Step 4: randomise across regions

Put the regions in an array and draw one before each request:

Pick a random exit region
REGIONS=(hangzhou ningbo)
REGION=${REGIONS[$((RANDOM % ${#REGIONS[@]}))]}

curl -s --http1.1 -H 'Connection: close' \
  -x proxy.gaofengxt.cn:20001 \
  -U "ipx_demo8f2k-zone-mob-region-$REGION:YOUR_PASSWORD" \
  https://ipinfo.io/ip

Keep the list aligned with the regions that have online devices in the console. Hard-coding an empty region simply fails that request.

Step 5: the full script (copy as is)

The steps above, combined into a repeatable self test: random region, forced new connection, one exit IP per line, and a count of how many distinct exits you hit.

How to run it

  1. 1
    vi test_proxy.sh

    Create the file and paste the whole script below into it.

  2. 2
    chmod +x test_proxy.sh

    Make it executable.

  3. 3
    export PROXY_PASSWORD='your-proxy-password'

    The password travels by environment variable only — never in the file, never in Git.

  4. 4
    PROXY_USER_PREFIX=your-account-zone-mob-region COUNT=20 ./test_proxy.sh

    Swap in your own prefix and run. Running it bare also works and uses the defaults below.

test_proxy.sh · random-exit self test
#!/usr/bin/env bash
# Random-exit self test: pick a random exit region per connection, print the real exit IP, then summarise.
set -u

# 1. Gateway address (HTTP port). The console is the source of truth.
PROXY_ADDRESS=${PROXY_ADDRESS:-proxy.gaofengxt.cn:20001}
# 2. Username prefix = your proxy account + the fixed -zone-mob-region
PROXY_USER_PREFIX=${PROXY_USER_PREFIX:-ipx_demo8f2k-zone-mob-region}
# 3. Exit regions to randomise over, comma separated
REGIONS=${REGIONS:-hangzhou,ningbo}
# 4. Number of connections
COUNT=${COUNT:-10}
# 5. Per-connect and overall timeout in seconds
CONNECT_TIMEOUT=${CONNECT_TIMEOUT:-10}
MAX_TIME=${MAX_TIME:-20}
# 6. Probe URL. It must return a plain-text IP.
TEST_URL=${TEST_URL:-https://ipinfo.io/ip}

# Read the password from the environment only. Never commit it.
if [ -z "${PROXY_PASSWORD:-}" ]; then
  printf 'Error: set the PROXY_PASSWORD environment variable first.\n' >&2
  exit 2
fi

case "$COUNT" in
  ''|*[!0-9]*|0)
    printf 'Error: COUNT must be an integer greater than 0.\n' >&2
    exit 2
    ;;
esac

# Split the comma-separated regions into an array
old_ifs=$IFS
IFS=',' read -r -a region_list <<< "$REGIONS"
IFS=$old_ifs
region_count=${#region_list[@]}

if [ "$region_count" -eq 0 ]; then
  printf 'Error: REGIONS needs at least one region.\n' >&2
  exit 2
fi

# Temp file holds every region + exit IP pair; removed on exit
results_file=$(mktemp "${TMPDIR:-/tmp}/proxy-exit-ip.XXXXXX") || exit 1
trap 'rm -f "$results_file"' EXIT

successful=0
failures=0

printf 'gateway: %s\n' "$PROXY_ADDRESS"
printf 'exit regions: %s\n' "$REGIONS"
printf 'connections: %s\n\n' "$COUNT"

for ((attempt = 1; attempt <= COUNT; attempt++)); do
  # Pick a random exit region. No -session- in the username means rotating mode.
  region=${region_list[$((RANDOM % region_count))]}
  proxy_username="${PROXY_USER_PREFIX}-${region}"

  # --http1.1 + Connection: close force a fresh connection; a reused one keeps the same exit
  response=$(curl \
    --silent --show-error --fail --http1.1 \
    --connect-timeout "$CONNECT_TIMEOUT" \
    --max-time "$MAX_TIME" \
    -H 'Connection: close' \
    -H 'Proxy-Connection: close' \
    -x "$PROXY_ADDRESS" \
    -U "$proxy_username:$PROXY_PASSWORD" \
    "$TEST_URL" 2>&1)
  curl_status=$?

  if [ "$curl_status" -ne 0 ]; then
    failures=$((failures + 1))
    printf '[%03d/%03d] region=%-10s failed curl=%d: %s\n' \
      "$attempt" "$COUNT" "$region" "$curl_status" "$response"
    continue
  fi

  # Plain-text IP: trim whitespace, then check only digits and dots remain
  exit_ip=$(printf '%s' "$response" | tr -d '[:space:]')
  case "$exit_ip" in
    ''|*[!0-9.]*)
      failures=$((failures + 1))
      printf '[%03d/%03d] region=%-10s failed: the probe URL did not return a plain-text IP\n' \
        "$attempt" "$COUNT" "$region"
      continue
      ;;
  esac

  successful=$((successful + 1))
  printf '%s\t%s\n' "$region" "$exit_ip" >> "$results_file"
  printf '[%03d/%03d] region=%-10s exit_ip=%s\n' \
    "$attempt" "$COUNT" "$region" "$exit_ip"
done

# More unique region/exit-IP pairs means more distinct devices were used
if [ "$successful" -gt 0 ]; then
  unique_pairs=$(sort -u "$results_file" | wc -l | tr -d ' ')
else
  unique_pairs=0
fi

printf '\nsummary: ok=%d failed=%d unique region/exit-IP pairs=%d\n' "$successful" "$failures" "$unique_pairs"

if [ "$successful" -gt 0 ]; then
  sort "$results_file" | uniq -c | awk '{
    printf "  region=%s exit_ip=%s hits=%d\n", $2, $3, $1
  }'
fi

# Exit 1 when anything failed so CI and monitors can react
if [ "$failures" -gt 0 ]; then
  exit 1
fi

Environment variables

VariableDefaultDescription
PROXY_PASSWORDProxy password. Required; the script exits without it.
PROXY_ADDRESSproxy.gaofengxt.cn:20001Gateway address (HTTP port).
PROXY_USER_PREFIXipx_demo8f2k-zone-mob-regionUsername prefix: your proxy account plus the fixed infix. The script appends -<region>.
REGIONShangzhou,ningboExit regions to randomise over, comma separated.
COUNT10How many connections to open in total.
CONNECT_TIMEOUT10Per-connection timeout in seconds. Mobile exits handshake slower than datacentres.
MAX_TIME20Overall per-request timeout in seconds, including the target's response time.
TEST_URLhttps://ipinfo.io/ipProbe URL. It must return a plain-text IP; your own endpoint works too, but then no IP can be parsed.

If the default probe is unreachable from a mobile exit, use one of these instead: https://api.ipify.org · http://ip.3322.net

What a run looks like

Sample output (IPs are illustrative)
gateway: proxy.gaofengxt.cn:20001
exit regions: hangzhou,ningbo
connections: 5

[001/005] region=hangzhou   exit_ip=117.136.38.201
[002/005] region=ningbo     exit_ip=223.104.211.86
[003/005] region=ningbo     exit_ip=223.104.211.86
[004/005] region=hangzhou   exit_ip=39.144.72.14
[005/005] region=hangzhou   exit_ip=117.136.38.201

summary: ok=5 failed=0 unique region/exit-IP pairs=3
  region=hangzhou exit_ip=117.136.38.201 hits=2
  region=hangzhou exit_ip=39.144.72.14 hits=1
  region=ningbo exit_ip=223.104.211.86 hits=2

Exit codes

  • 0Everything succeeded.
  • 1At least one attempt failed; see the per-line output.
  • 2Bad arguments or no PROXY_PASSWORD — no request was sent.

Step 6: move it into your code

The same two rules as with curl: no session in the username, and a fresh connection per request.

Python · requests
import random
import requests
from urllib.parse import quote

GATEWAY = "proxy.gaofengxt.cn:20001"  # gateway HTTP port
ACCOUNT = "ipx_demo8f2k"
PASSWORD = quote("YOUR_PASSWORD", safe="")  # escape @ : / in the password
REGIONS = ["hangzhou", "ningbo"]


def fetch(url: str) -> str:
    region = random.choice(REGIONS)  # random exit region
    username = f"{ACCOUNT}-zone-mob-region-{region}"  # no session = rotating mode
    proxy = f"http://{username}:{PASSWORD}@{GATEWAY}"
    # Connection: close forces a fresh connection so the exit changes
    resp = requests.get(
        url,
        proxies={"http": proxy, "https": proxy},
        headers={"Connection": "close"},
        timeout=20,
    )
    resp.raise_for_status()

    return resp.text.strip()


for _ in range(5):
    print(fetch("https://ipinfo.io/ip"))
Node.js · undici
// Node 18+; install first: npm i undici
import { ProxyAgent } from "undici";

const GATEWAY = "proxy.gaofengxt.cn:20001";
const ACCOUNT = "ipx_demo8f2k";
const PASSWORD = encodeURIComponent("YOUR_PASSWORD");
const REGIONS = ["hangzhou", "ningbo"];

async function fetchViaRandomExit(url) {
  // Random exit region; a username without session means rotating mode
  const region = REGIONS[Math.floor(Math.random() * REGIONS.length)];
  const username = `${ACCOUNT}-zone-mob-region-${region}`;
  // A fresh agent per request; a pooled connection keeps the same exit
  const agent = new ProxyAgent(`http://${username}:${PASSWORD}@${GATEWAY}`);

  try {
    const res = await fetch(url, { dispatcher: agent });

    return (await res.text()).trim();
  } finally {
    await agent.close();
  }
}

for (let i = 0; i < 5; i++) {
  console.log(await fetchViaRandomExit("https://ipinfo.io/ip"));
}

Troubleshooting

Match the exact wording your terminal prints on the left.

What you seeCauseWhat to do
curl: (5) Could not resolve proxyThe gateway hostname did not resolve.Check local DNS and connectivity, and copy the address from the console rather than typing it.
curl: (7) Failed to connectThe name resolved but the port refused the connection.Confirm the port (HTTP and SOCKS5 differ), then check local firewalls or corporate egress rules.
curl: (22) … 407Proxy authentication failed.The username must include the whole -zone-mob-region-<region> segment; check for stray spaces and update the password if it was reset.
curl: (28) Operation timed outConnected, but no response in time — usually mobile jitter or a slow target.Raise CONNECT_TIMEOUT / MAX_TIME and add retries in your code.
503 · NO_ELIGIBLE_DEVICEThat region currently has no online device.Use another region, or check the console for regions that still have devices.
503 · TUNNEL_UNAVAILABLEDevices exist, but the tunnel could not be established (exit-side network problem).Retry or switch region; if it persists, contact support with the timestamp and region.
403 · TARGET_DENIEDThe destination matches the platform's outbound blocklist.These requests are not retried on another device. Use a different destination, or contact support if the use case is legitimate.
It works, but the IP never changesThe username carries a session, or the connection is being reused.Drop -session- and -sessTime-, add --http1.1 and Connection: close, and stop reusing one connection pool in code.

Before you scale this up

  • Pass the password through the environment. Keep it out of scripts and out of your repository, especially when the script is shared.
  • Self tests consume metered traffic. Think about the bill before setting COUNT to several hundred.
  • If you need one stable exit IP for a while (a login flow, a checkout), that is a sticky session rather than rotation — see the Proxy access section of the API docs.
  • The IP the probe returns is exactly what the target site sees; validate region requirements against that address.

Working? Here is what to read next

Sticky sessions, the REST API, and error codes live in the main documentation; live regions and credentials live in the console.