How to Test Proxy Speed and Latency Properly
A residential proxy adds two uncontrolled network legs to every request, so speed has to be measured statistically. Here is the curl harness, the percentile maths and the sampling rules.
Key takeaways
- Proxy speed is four separate measurements: connect time to the gateway, handshake time through the exit, time to first byte, and throughput during body transfer.
- Report p50 and p95 rather than a mean, because residential latency distributions have a long right tail and the tail is what stalls a concurrent crawler.
- Every
curltiming variable is cumulative from the start of the request, so each phase duration is a subtraction between two of them. - Testing from one machine in one region measures your distance to one gateway as much as it measures the pool, and provider rankings change when you move the probe.
- Your timeout, concurrency level and target endpoint are part of the result, so percentiles produced under different settings cannot be compared.
The question "is this proxy fast?" has no answer until you say which number you mean. A pool can deliver first bytes in 300 ms and still take nine seconds to finish a 2 MB page. Another can post a respectable average while a tenth of its requests hang past your timeout and quietly leave the sample.
Latency through a residential proxy is the sum of three legs: your host to the gateway, the gateway to a consumer device, and that device to the target. Two of the three run over infrastructure nobody in this transaction controls. That makes proxy speed a distribution rather than a value, and it makes the measurement design more important than the tooling.
Four numbers hide behind "proxy speed"
| Measurement | What it covers | What usually dominates it |
|---|---|---|
| Connect time | TCP handshake with the proxy gateway | Your host's distance to the gateway region you were assigned |
| Handshake time | The CONNECT tunnel plus the TLS handshake with the target | Round trips over the exit device's consumer uplink, several of them |
| Time to first byte | Request sent to first response byte received | Exit uplink latency plus the target's own processing time |
| Total time | Connection open to last byte received | TTFB plus body transfer, so it scales with page weight |
| Throughput | Bytes per second during the body transfer | Household upload capacity and whatever else that household is doing |
For scraping, TTFB is the number to optimise, because most scraped responses are small enough that transfer time is noise. Throughput matters when you pull large files or render full pages with their assets, and it is also the number your bandwidth bill tracks, which makes it worth watching for reasons covered in reduce proxy bandwidth costs.
Timing a single request with curl
You do not need a tool. curl exposes every phase boundary through write-out variables, and one command gives you a complete timing breakdown of a proxied request.
curl -sS -o /dev/null \
-x "http://USER-cc-us:[email protected]:7000" \
-w 'dns=%{time_namelookup} connect=%{time_connect} tls=%{time_appconnect} pre=%{time_pretransfer} ttfb=%{time_starttransfer} total=%{time_total} code=%{http_code} bytes=%{size_download}\n' \
https://target.example/probe-64kReading the phase boundaries
- `time_namelookup` covers DNS for the gateway hostname, resolved on your own machine. It drops to near zero once your resolver caches, which is why the first request of a run belongs in a discarded warm-up.
- `time_connect` is the point at which the TCP handshake with the gateway completed. Subtract
time_namelookupto get the handshake itself, which is a clean measure of your distance to the gateway. - `time_appconnect` is the point at which the
CONNECTtunnel and the TLS handshake with the target both completed. The differencetime_appconnect - time_connectis the single most informative figure for a proxy, because it contains full round trips over the exit device's uplink with no target processing mixed in. - `time_starttransfer` is TTFB. Subtract
time_appconnectand you have the target's think time plus one more round trip through the exit. - `time_total - time_starttransfer` is body transfer. Divide
size_downloadby that and you get real throughput, which is more useful than curl'sspeed_downloadbecause the latter averages across the whole request including the handshakes.
Percentiles, because the mean lies
Residential latency distributions are right-skewed. Most requests come back promptly and a minority take five to ten times longer, because the exit was on a congested uplink or tethered to a phone. A mean sits above the typical experience and well below the pain, so it describes neither.
The p50 tells you what a normal request feels like. The p95 tells you what your worker pool will stall on, and for a concurrent crawler that is the governing number: a run with 50 workers and a 10-second timeout is paced by the requests that hold a worker hostage, not by the fast ones. Look at p99 too if your job has a hard deadline.
- Use nearest-rank percentiles computed from raw samples. Sort the values and take the value at index
ceil(n * p). - Never average percentiles across runs or regions. A p95 of two p95s is not a p95. Pool the raw samples and recompute.
- Never compute percentiles from bucketed averages. Pre-aggregated metrics systems will happily hand you a number that has no relationship to any request that occurred.
- Always publish the success rate beside the percentiles. Timeouts leave the latency sample, so a pool that fails 10% of requests and posts a 400 ms p95 on the rest is slower than the number implies. That interaction is covered in proxy success rate explained.
Where you test from decides what you measure
A single probe host measures the route from that host to one gateway point of presence, and only then the pool behind it. Run the same test from Frankfurt and from Virginia and the provider ordering can invert, because gateway infrastructure and exit supply are unevenly distributed and each provider is strong somewhere different.
Pointing a US host at an EU gateway to reach EU exits adds a transatlantic leg twice over, and the result says more about your probe placement than about the pool. Test from the region you will deploy from, and if that is several regions, keep the results separate. We run continuous tests from both US and EU infrastructure and publish per-region figures alongside a combined view; the setup is in our methodology.
Your own host is part of the measurement too. A cheap shared VPS with a saturated interface contributes its own tail, and you will attribute it to the proxy. Run a direct, unproxied request to the same target on the same schedule as a control, and if the control moves, discard the window.
DNS resolution happens somewhere too
With an HTTP proxy and a CONNECT tunnel, you resolve only the gateway hostname. The target hostname is resolved by the exit, using whatever resolver that household has, which might be an ISP resolver, a public one, or something that rewrites answers. That choice decides which CDN edge you reach, and therefore a large part of the latency you are about to measure.
SOCKS5 makes the decision explicit. In curl, a socks5:// proxy resolves the hostname locally, while socks5h:// and --socks5-hostname hand resolution to the proxy. Resolve locally and you can end up sending traffic from a German exit to a US edge, which produces a number with no operational meaning. Keep resolution at the exit, and record the edge the target reports if it exposes one. The protocol differences are covered in HTTP vs SOCKS5 proxies.
Warm connections versus cold connections
The first request over a new exit pays DNS, a TCP handshake, tunnel setup and a TLS handshake, plus whatever time the gateway spends selecting an exit. Later requests on the same connection pay only TTFB. Those are two different numbers and both are real, so report them separately and label them: cold means a new connection per request, warm means a reused one.
This is where session mode leaks into your speed test. Rotating pools are mostly cold by construction, since a new connection is the thing that triggers a new exit. Sticky sessions amortise the setup cost across a task. Comparing a sticky measurement of one provider against a rotating measurement of another is meaningless, and it is easy to do when two providers default differently; rotating vs sticky proxy sessions covers how the modes are selected.
TLS session resumption and HTTP/2 multiplexing blur the line further, so disable resumption when you want reproducible cold numbers. Discard a fixed number of warm-up samples per process and state how many you discarded, because that count changes the p95.
Sample size and how long to run
A stable p95 needs at least a few hundred successful samples per provider, per region and per target. A p99 needs an order of magnitude more, which is why most short benchmarks that quote one should not be trusted.
Duration matters as much as count. Exits are consumer devices, so their available uplink follows household activity: evening congestion in the exit country is a real and repeatable effect, and a ten-minute run at 03:00 UTC is not a result. Run at least a full day before you believe an ordering, then re-check across a week. Interleave providers request by request rather than in blocks, so a target-side hiccup does not land entirely on one pool.
Controlling the target endpoint
Whatever you request contributes its own processing time, its own edge location and its own rate limiting. If the target is unstable, you are measuring the target. Four rules keep it out of the way.
- Use an endpoint that returns a fixed-size body quickly, is served from a global CDN, and does not treat proxies as hostile. We use Cloudflare-hosted endpoints so the target leg is as short and consistent as the exit allows.
- Measure two body sizes: something small for TTFB, and a fixed larger object, typically 1 MB, for throughput. The same object every time, so byte counts are comparable.
- Pin
Accept-Encodingexplicitly. A response whose compressed size varies between runs makes throughput incomparable, and a target that changes its compression settings will look like a provider regression. - Never benchmark latency against the site you actually intend to scrape. It rate-limits, it caches unevenly by path, and it will make one provider look bad for reasons that belong to your request pattern rather than to the pool.
Measuring under concurrency
Single-request timings tell you what is possible. Your crawler runs many requests at once, and that is where thin pools reveal themselves. A short harness is enough: fire a fixed number of requests at a fixed parallelism, keep only the successes, and compute percentiles from the raw values.
# 600 requests, 20 in flight, one distinct sticky session per request
seq 1 600 | xargs -P 20 -I SEQ curl -sS -o /dev/null --max-time 30 \
-x "http://USER-cc-us-sessid-SEQ:[email protected]:7000" \
-w '%{http_code} %{time_starttransfer} %{time_total}\n' \
https://target.example/probe-64k >> samples.txt
# nearest-rank percentiles over the successful requests only
awk '$1 == 200 { print $2 }' samples.txt | sort -n | awk '
function q(p, i) { i = int(n * p); if (n * p > i) i++; return v[i] }
{ v[NR] = $1; n = NR }
END { printf "n=%d p50=%.3fs p90=%.3fs p95=%.3fs\n", n, q(0.50), q(0.90), q(0.95) }'Then run the same 600 requests at parallelism 1, 5, 20 and 50 and plot p95 against concurrency. A pool with genuine depth in your target country stays roughly flat. A pool that is thin there degrades sharply, because you begin queueing behind the same devices, and the shape of that curve tells you more than any single latency figure. Pool depth is the underlying variable, discussed in proxy pool size explained.
Record the failure classes at each concurrency level alongside the timings. Per-exit concurrency caps on the provider side surface as gateway errors rather than as slowness, so a pool can look fast at parallelism 50 purely because a third of the requests were refused before they reached the target.
Turning the numbers into a decision
A defensible shortlist reports, per provider and per region: p50 and p95 TTFB, throughput on a fixed 1 MB object, and success rate, all at a stated concurrency and timeout, gathered over at least 24 hours. Weight them by your workload. Many small requests means p95 TTFB decides; a few large downloads means throughput and its low percentile decide.
Then fold in price, because a pool that is 30% slower at half the cost still wins when your constraint is bandwidth spend rather than wall-clock time. Structuring that full comparison, including trial design and pricing normalisation, is the job of how to benchmark proxy providers; this post covers the network measurement layer underneath it.
If you would rather read numbers than collect them, we run this measurement continuously against every pool we track and publish it on the live benchmark table, with rolling history on the downloadable performance graphs and a standing ranking in fastest residential proxies. The gateway endpoints and regions we probe are listed on each provider profile, for example Ping Proxies.
Frequently asked questions
How do I test proxy speed with curl?
Send a request through the proxy with -x and print the timing variables with -w, using time_connect, time_appconnect, time_starttransfer and time_total. All four are cumulative from the start of the request, so each phase is a subtraction. Repeat a few hundred times against a fixed endpoint, keep only successful responses, and compute p50 and p95 rather than an average.
What is a good latency for a residential proxy?
Because the exit is a consumer connection, sub-second time to first byte from a nearby region is good and one to two seconds is common. We treat 1.5 seconds or under as the healthy band in our own scoring. The p95 matters more than the median, since a long tail is what stalls concurrent workers, and the figure shifts with exit country and time of day.
Should I compare proxies on average latency or p95?
Use p95, and report p50 next to it. Residential latency is right-skewed, so a mean sits between the typical experience and the tail and describes neither. The p95 predicts how often a worker in a concurrent crawler will be held up long enough to matter. Always publish the timeout you used, because a tighter timeout mechanically improves every percentile.
Why do my proxy speed test results change through the day?
Residential exits are devices in homes, so their spare upload capacity follows household activity. Evening congestion in the exit country is a repeatable effect, and pool composition shifts as devices come online and drop off. A ten-minute test captures one slice of that. Run at least 24 hours, then re-check across a week before trusting an ordering.
Can I use an online speed test site to measure a proxy?
Not usefully. Consumer speed test sites pick their own nearby server, run multi-stream transfers tuned to look good, and often refuse proxied traffic outright. They also report nothing about time to first byte, which is the number that governs scraping. Use a fixed CDN-hosted object of known size and time the transfer yourself.
See how the providers actually perform
Our benchmark tests 18 residential proxy providers around the clock from US and EU infrastructure. Success rate, latency, fraud score and price per 100GB, refreshed every five minutes.
Related guides
How to Benchmark Proxy Providers: A Repeatable Test Plan
Run every provider at the same time, against the same targets, from the same machine, and log enough that you can compute cost per successful request when the trial ends.
Read the guideProxy Success Rate Explained: What Counts as Good?
A provider quoting 99.9% and a scraper seeing 78% can both be telling the truth. The difference is the predicate, the denominator and the target, and all three are yours to fix.
Read the guideRotating vs Sticky Proxy Sessions: How to Choose
Rotation is a property of your proxy username, not of your code. Here is what each mode does to a request, the failure signature of each, and the workload table that settles the argument.
Read the guide