How to Benchmark Residential Proxies Without Fooling Yourself

On this page
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:
| Control | Why it matters |
|---|---|
| Client, library, and version | Timeouts, pooling, and TLS behavior differ by stack |
| Runtime region | Your path to the gateway is part of the measurement |
| Proxy endpoint and scheme | HTTPS, SOCKS5, and HTTP are different contracts |
| Connection policy | Keep-alive vs one connection per attempt changes exits and latency |
| Requested country | Availability and path length are country-specific |
| Rotation vs sticky | Continuity and failure modes are different products |
| Destination set | ipinfo.io is not a catalog page; a browser is not cURL |
| Sample window | Residential supply and destination behavior move through the day |
| Parser and validation rules | HTTP 200 with the wrong payload is not a valid result |
| Deadline and retry budget | Unbounded 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:
| Outcome | Count it as | Typical next step |
|---|---|---|
valid_result | Success | Commit the observation |
auth_failed | Harness defect | Stop the run; fix credentials |
proxy_connect_failed | Transport | Check endpoint, port, firewall, client scheme |
country_unavailable | Availability | Bounded backoff; do not drop the country |
country_mismatch | Integrity failure | Do not accept the payload as in-country |
destination_throttled | Destination | Reduce pressure; follow destination policy |
destination_failed | Transient or origin | Retry only inside the job budget |
session_lost | Sticky failure | New session label; restart the workflow |
parse_failed | Content | Inspect payload and parser version |
validation_failed | Content | Quarantine; do not score as success |
deadline_exceeded | Budget | Close 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.