← Blog · · 9 min read

How to Benchmark Residential Proxies Without Fooling Yourself

A residential proxy “benchmark” is easy to fake by accident. Ten requests that return ten IPs, a single mean latency, or a vendor chart taken from a different client, destination, and time of day will not tell you whether a production job will finish with correct, timely, cost-efficient output.

A useful benchmark is an experiment. It names the unit of work, holds the controls still, records outcomes instead of slogans, and publishes enough method that another operator can repeat the run.

This is not a provider ranking. It is a protocol you can run against PacketStream—or any other residential gateway—before you trust a number.

Decide what one success means

Do not start with requests. Start with the logical job the business needs: one public product observation, one localized search check, one fare snapshot, one ad-verification capture.

Write that definition down before the first timed call:

job          = one expected observation
success      = destination responded AND the parsed result passed validation
               AND the observed country matched the requested country
attempt      = one proxy-backed HTTP exchange used to pursue that job
connection   = one TCP/TLS session to the proxy gateway

A job can take several attempts. Several attempts can share one connection. PacketStream automatic rotation selects a residential exit when the client opens a new proxy connection, not when the application constructs another request object. If you treat those three layers as the same thing, the benchmark will describe your HTTP pool, not the network.

Freeze the controls

Two runs are comparable only when the following stay fixed—or are reported as changed:

ControlWhy it matters
Client, library, and versionTimeouts, pooling, and TLS behavior differ by stack
Runtime regionYour path to the gateway is part of the measurement
Proxy endpoint and schemeHTTPS, SOCKS5, and HTTP are different contracts
Connection policyKeep-alive vs one connection per attempt changes exits and latency
Requested countryAvailability and path length are country-specific
Rotation vs stickyContinuity and failure modes are different products
Destination setipinfo.io is not a catalog page; a browser is not cURL
Sample windowResidential supply and destination behavior move through the day
Parser and validation rulesHTTP 200 with the wrong payload is not a valid result
Deadline and retry budgetUnbounded retries convert failures into bandwidth

Record the PacketStream endpoint you actually used. The recommended HTTPS proxy is proxy.packetstream.io:31111. SOCKS5 is proxy.packetstream.io:31113. HTTP on 31112 exists for compatibility and should not be the default for a new harness.

Keep credentials out of the command line and out of the result file. Use PACKETSTREAM_USER and PACKETSTREAM_AUTH_KEY.

Stop using vanity scores as headlines

These numbers are useful diagnostics. They are poor scoreboard titles.

Unique IPs in a sample of 10. Automatic rotation does not promise a distinct address on every connection. A small sample with repeats is not evidence that rotation failed. A small sample with no repeats is not evidence that it will hold at 10,000 jobs.

Mean latency. One slow tail event can ruin a crawl that still looks fine on the average. Report p50, p95, and p99, and split the stages: proxy connect, TLS, time to first byte, and total.

HTTP success rate. A 200 that fails schema validation, returns a challenge page, or arrives from the wrong country is not a completed job.

City or coordinates from an IP database. PacketStream’s supported targeting boundary is the ISO country. City, region, postal code, and coordinates are approximate diagnostic data.

A single destination. A lightweight inspection URL tells you the gateway, credentials, and observed exit work. It does not estimate bandwidth, parser yield, or destination throttling for the real workload.

Use an outcome taxonomy, not a boolean

Give every attempt a single primary outcome. Keep the set small enough to operate:

OutcomeCount it asTypical next step
valid_resultSuccessCommit the observation
auth_failedHarness defectStop the run; fix credentials
proxy_connect_failedTransportCheck endpoint, port, firewall, client scheme
country_unavailableAvailabilityBounded backoff; do not drop the country
country_mismatchIntegrity failureDo not accept the payload as in-country
destination_throttledDestinationReduce pressure; follow destination policy
destination_failedTransient or originRetry only inside the job budget
session_lostSticky failureNew session label; restart the workflow
parse_failedContentInspect payload and parser version
validation_failedContentQuarantine; do not score as success
deadline_exceededBudgetClose the job unsuccessful

country_unavailable and country_mismatch are different events. PacketStream fails a request when the requested country has no available exit. It does not silently substitute another country. If your client retries by stripping _country-US from the auth key, the benchmark will invent a success the network did not offer.

Measure these six numbers

Compute them from jobs, then keep attempt-level detail underneath.

1. Valid job yield

valid job yield = valid completed jobs / jobs started

This is the headline. Everything else explains it.

2. Country correctness

For country-targeted jobs:

country correctness = jobs whose observed country equals the requested ISO code
                    / jobs that received an exit

Verify the country with the same inspection method every time, then collect the real destination only after that check passes. Do not infer the country later from a URL or from a city field.

3. Connection-aware rotation

When the run uses automatic rotation, log whether each attempt reused a connection or opened a new one. Then report:

  • distinct exits per new connection, not per request;
  • the reuse rate of the HTTP pool;
  • that uniqueness was not required.

A fair rotation check uses separate processes or an explicit --no-keepalive policy. A pooled client that keeps one connection open is doing what HTTP clients do.

4. Sticky-session continuity

When the run uses a sticky session, append a non-sensitive label:

AUTH_KEY_session-bench42
AUTH_KEY_country-US_session-bench42

Measure:

  • whether repeated new connections with the same label kept the same exit;
  • how the run behaved when the session failed.

A PacketStream sticky session can retain an exit across a rolling 60-minute inactivity window: each new connection assignment restarts the timer. Requests on an already-open connection do not restart it. If the supplying Packeter disconnects, the session fails. The network does not silently hand the same label to a different household. Score that as session_lost and recover with a new label from a known checkpoint—not with a tight retry loop on the dead label.

5. Latency percentiles, by stage

For each attempt, record connect, first byte, and total duration. Publish p50/p95/p99 for:

  • jobs that ended valid_result;
  • jobs that failed, broken out by outcome.

Never mix inspection-URL timings into the same table as full page or browser timings.

6. Bytes per valid result

bytes per valid result = proxy bytes transferred / valid completed jobs

Include retries, redirects, and failed attempts that still moved bytes. Exclude jobs you never started. This number, not a marketing GB price, is what you multiply by your own rate when you want cost per valid result. Protocol overhead and dashboard billing can differ from client-reported sizes; calibrate against the dashboard during a bounded pilot instead of treating cURL size_download as an invoice.

Keep the first harness small

A first pass should use the same machine that will run production workers, the recommended HTTPS endpoint, and a destination you are authorized to measure. Separate the body from the timing record:

read -r -p 'PacketStream username: ' PACKETSTREAM_USER
read -r -s -p 'PacketStream auth key: ' PACKETSTREAM_AUTH_KEY
printf '\n'
export PACKETSTREAM_USER PACKETSTREAM_AUTH_KEY

RESULT_DIR="$(mktemp -d "${TMPDIR:-/tmp}/proxy-bench.XXXXXX")"

curl --silent --show-error \
  --connect-timeout 15 \
  --max-time 45 \
  --proxy 'https://proxy.packetstream.io:31111' \
  --proxy-user "${PACKETSTREAM_USER}:${PACKETSTREAM_AUTH_KEY}_country-US" \
  --output "${RESULT_DIR}/ipinfo.json" \
  --write-out '{"http_code":%{http_code},"connect_seconds":%{time_connect},"ttfb_seconds":%{time_starttransfer},"total_seconds":%{time_total},"download_bytes":%{size_download},"num_connects":%{num_connects}}\n' \
  'https://ipinfo.io'

jq '{ip, country, org}' "${RESULT_DIR}/ipinfo.json"

Treat that timing row as one observation from one runtime, not as a network-wide SLA.

For rotation, force a connection boundary:

for attempt in 1 2 3 4 5; do
  curl --silent --show-error --fail-with-body \
    --no-keepalive \
    --proxy 'https://proxy.packetstream.io:31111' \
    --proxy-user "${PACKETSTREAM_USER}:${PACKETSTREAM_AUTH_KEY}" \
    'https://ipinfo.io' \
    | jq -r '[.ip, .country, .org] | @tsv'
done

For a sticky check, reuse one non-sensitive label across several new connections and compare the observed IP. If the session fails, start a new label. Do not keep hammering the failed one.

A production-shaped run then replaces ipinfo.io with a small, authorized sample of the real destination set, applies the real parser, and scores valid_result only when validation passes.

Report a result others can rerun

A published number without a method is an anecdote. Attach a reproducibility card to every run:

{
  "ran_at": "2026-08-03T15:00:00Z",
  "runtime_region": "us-west",
  "client": "curl/8.x",
  "endpoint": "https://proxy.packetstream.io:31111",
  "requested_country": "US",
  "session_mode": "rotate",
  "connection_policy": "no-keepalive",
  "destination_class": "inspection-url",
  "jobs_started": 200,
  "valid_jobs": 184,
  "valid_job_yield": 0.92,
  "country_correct": 184,
  "p50_total_ms": 900,
  "p95_total_ms": 2400,
  "bytes_per_valid_result": 78000,
  "notes": "Inspection URL only. Not a catalog-page estimate."
}

Leave credentials, full proxy URLs, session labels that contain personal data, and destination account cookies out of the card.

If you change any control—browser instead of cURL, pooled connections, a second country, a different hour—call it a new run. Do not average incompatible experiments into one “score.”

What this protocol will not prove

A careful residential-proxy benchmark will not prove that an exit is undetectable, that a named anti-bot product will accept the traffic, that every connection receives a unique IP, or that city-level geography is exact. It will not produce a universal ranking that survives a change of destination, parser, or client pool.

It will tell you whether this workload, from this runtime, through this endpoint and country rule, produced valid results at a known yield, latency tail, and byte cost—and whether failures were availability, identity, destination, or application problems.

That is enough to decide if a pipeline is ready to scale, and it is more than a unique-IP screenshot can offer.

For the product contracts behind the controls, see endpoints and authentication, rotation and sticky sessions, and country targeting. For a first connectivity check, use the cURL preflight. Connection reuse is covered in how HTTP connection pooling affects rotation.