How to Test Proxy Speed and Reliability: DIY Benchmark Guide
Last updated: April 2026 | By Hex Proxies Team
Every proxy provider claims fast speeds, high uptime, and excellent success rates. Very few provide the data to back those claims, and the benchmarks they do publish are run under conditions designed to produce favorable results. The only reliable way to evaluate a proxy provider is to run your own benchmarks against your actual target sites.
This guide provides a complete DIY benchmark framework: what to measure, how to measure it, and how to interpret results. All code examples are production-ready and designed to produce statistically meaningful results.
What to Measure
A comprehensive proxy benchmark covers five metrics:
| Metric | What It Tells You | Good Threshold | Warning Threshold |
|---|---|---|---|
| Latency (median) | Typical response time | <300ms | >500ms |
| Latency (P95) | Worst-case response time | <1000ms | >2000ms |
| Success rate | Percentage of HTTP 200 responses | >90% | <80% |
| Throughput | Requests per second sustained | Depends on plan | <50% of expected |
| IP diversity | Unique IPs per 100 requests | >90 (rotating) | <70 (rotating) |
The Benchmark Script
Core Benchmark Framework
#!/usr/bin/env python3
"""Proxy Benchmark Framework
Usage:
python proxy_benchmark.py \
--proxy-url http://user:pass@gate.hexproxies.com:8080 \
--requests 200 \
--concurrency 10
Requirements:
pip install aiohttp
"""
import aiohttp
import asyncio
import argparse
import json
import statistics
import time
from dataclasses import dataclass, asdict
from typing import List, Optional
from datetime import datetime, timezone
# Target sites for benchmarking (mix of protection levels)
TARGET_SITES = [
{"url": "https://httpbin.org/ip", "name": "httpbin (unprotected)"},
{"url": "https://www.example.com", "name": "example.com (unprotected)"},
{"url": "https://www.amazon.com", "name": "Amazon (moderate)"},
{"url": "https://www.google.com/search?q=proxy+test", "name": "Google Search (moderate)"},
{"url": "https://www.cloudflare.com", "name": "Cloudflare (protected)"},
]
@dataclass(frozen=True)
class RequestResult:
"""Immutable result of a single proxy request."""
target: str
status: int
latency_ms: float
proxy_ip: str
content_length: int
error: Optional[str]
timestamp: str
@dataclass(frozen=True)
class BenchmarkSummary:
"""Immutable benchmark summary statistics."""
total_requests: int
successful_requests: int
failed_requests: int
success_rate: float
latency_median_ms: float
latency_p95_ms: float
latency_p99_ms: float
latency_min_ms: float
latency_max_ms: float
unique_ips: int
total_duration_s: float
requests_per_second: float
async def single_request(
session: aiohttp.ClientSession,
url: str,
proxy_url: str,
target_name: str
) -> RequestResult:
"""Execute a single request through the proxy and measure performance."""
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/124.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br"
}
start = time.monotonic()
proxy_ip = "unknown"
try:
async with session.get(
url,
proxy=proxy_url,
headers=headers,
timeout=aiohttp.ClientTimeout(total=30),
ssl=False # Skip SSL verification for benchmark
) as response:
content = await response.read()
latency = (time.monotonic() - start) * 1000
# Try to extract proxy IP from httpbin response
if "httpbin" in url:
try:
data = json.loads(content)
proxy_ip = data.get("origin", "unknown")
except (json.JSONDecodeError, UnicodeDecodeError):
pass
return RequestResult(
target=target_name,
status=response.status,
latency_ms=round(latency, 2),
proxy_ip=proxy_ip,
content_length=len(content),
error=None,
timestamp=datetime.now(timezone.utc).isoformat()
)
except Exception as e:
latency = (time.monotonic() - start) * 1000
return RequestResult(
target=target_name,
status=0,
latency_ms=round(latency, 2),
proxy_ip="unknown",
content_length=0,
error=str(e),
timestamp=datetime.now(timezone.utc).isoformat()
)
async def run_benchmark(
proxy_url: str,
num_requests: int = 200,
concurrency: int = 10
) -> tuple:
"""Run the full benchmark suite. Returns (results, summary)."""
semaphore = asyncio.Semaphore(concurrency)
results: List[RequestResult] = []
async def bounded_request(session, url, name):
async with semaphore:
result = await single_request(session, url, proxy_url, name)
return result
connector = aiohttp.TCPConnector(limit=concurrency * 2, ssl=False)
async with aiohttp.ClientSession(connector=connector) as session:
# Build request list: distribute evenly across targets
tasks = []
for i in range(num_requests):
target = TARGET_SITES[i % len(TARGET_SITES)]
tasks.append(
bounded_request(session, target["url"], target["name"])
)
start_time = time.monotonic()
results = list(await asyncio.gather(*tasks))
total_duration = time.monotonic() - start_time
# Calculate summary statistics
successful = [r for r in results if r.status == 200]
latencies = [r.latency_ms for r in successful]
unique_ips = len(set(
r.proxy_ip for r in results
if r.proxy_ip != "unknown"
))
if latencies:
sorted_latencies = sorted(latencies)
p95_idx = int(len(sorted_latencies) * 0.95)
p99_idx = int(len(sorted_latencies) * 0.99)
summary = BenchmarkSummary(
total_requests=len(results),
successful_requests=len(successful),
failed_requests=len(results) - len(successful),
success_rate=round(len(successful) / len(results) * 100, 2),
latency_median_ms=round(statistics.median(latencies), 2),
latency_p95_ms=round(sorted_latencies[p95_idx], 2),
latency_p99_ms=round(sorted_latencies[p99_idx], 2),
latency_min_ms=round(min(latencies), 2),
latency_max_ms=round(max(latencies), 2),
unique_ips=unique_ips,
total_duration_s=round(total_duration, 2),
requests_per_second=round(len(results) / total_duration, 2)
)
else:
summary = BenchmarkSummary(
total_requests=len(results),
successful_requests=0,
failed_requests=len(results),
success_rate=0.0,
latency_median_ms=0.0,
latency_p95_ms=0.0,
latency_p99_ms=0.0,
latency_min_ms=0.0,
latency_max_ms=0.0,
unique_ips=0,
total_duration_s=round(total_duration, 2),
requests_per_second=0.0
)
return tuple(results), summary
def print_report(summary: BenchmarkSummary, results: tuple):
"""Print a formatted benchmark report."""
print("\n" + "=" * 60)
print("PROXY BENCHMARK REPORT")
print("=" * 60)
print(f"\nTotal Requests: {summary.total_requests}")
print(f"Successful: {summary.successful_requests}")
print(f"Failed: {summary.failed_requests}")
print(f"Success Rate: {summary.success_rate}%")
print(f"\nLatency (median): {summary.latency_median_ms}ms")
print(f"Latency (P95): {summary.latency_p95_ms}ms")
print(f"Latency (P99): {summary.latency_p99_ms}ms")
print(f"Latency (min): {summary.latency_min_ms}ms")
print(f"Latency (max): {summary.latency_max_ms}ms")
print(f"\nUnique IPs: {summary.unique_ips}")
print(f"Duration: {summary.total_duration_s}s")
print(f"Throughput: {summary.requests_per_second} req/s")
# Per-target breakdown
print("\n" + "-" * 60)
print("PER-TARGET BREAKDOWN")
print("-" * 60)
targets = set(r.target for r in results)
for target in sorted(targets):
target_results = [r for r in results if r.target == target]
target_success = [r for r in target_results if r.status == 200]
target_latencies = [r.latency_ms for r in target_success]
rate = (
len(target_success) / len(target_results) * 100
if target_results else 0
)
median = (
statistics.median(target_latencies)
if target_latencies else 0
)
print(f"\n {target}")
print(f" Success: {rate:.1f}% ({len(target_success)}/{len(target_results)})")
print(f" Median latency: {median:.0f}ms")
print("\n" + "=" * 60)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Proxy Benchmark Tool")
parser.add_argument(
"--proxy-url",
required=True,
help="Proxy URL (http://user:pass@host:port)"
)
parser.add_argument(
"--requests",
type=int,
default=200,
help="Total number of requests (default: 200)"
)
parser.add_argument(
"--concurrency",
type=int,
default=10,
help="Concurrent requests (default: 10)"
)
parser.add_argument(
"--output",
help="Save results to JSON file"
)
args = parser.parse_args()
print(f"Starting benchmark: {args.requests} requests, "
f"{args.concurrency} concurrent")
print(f"Proxy: {args.proxy_url.split('@')[1] if '@' in args.proxy_url else args.proxy_url}")
results, summary = asyncio.run(
run_benchmark(args.proxy_url, args.requests, args.concurrency)
)
print_report(summary, results)
if args.output:
output_data = {
"summary": asdict(summary),
"results": [asdict(r) for r in results]
}
with open(args.output, "w") as f:
json.dump(output_data, f, indent=2)
print(f"\nResults saved to {args.output}")
Running the Benchmark
Basic Usage
# Install dependency
pip install aiohttp
# Test Hex Proxies residential
python proxy_benchmark.py \
--proxy-url "http://your_user:your_pass@gate.hexproxies.com:8080" \
--requests 200 \
--concurrency 10 \
--output results_hex_residential.json
# Test another provider for comparison
python proxy_benchmark.py \
--proxy-url "http://user:pass@other-provider.com:8080" \
--requests 200 \
--concurrency 10 \
--output results_other.json
Sample Output
============================================================
PROXY BENCHMARK REPORT
============================================================
Total Requests: 200
Successful: 186
Failed: 14
Success Rate: 93.0%
Latency (median): 247.31ms
Latency (P95): 892.55ms
Latency (P99): 1432.18ms
Latency (min): 89.22ms
Latency (max): 2891.44ms
Unique IPs: 38
Duration: 62.41s
Throughput: 3.21 req/s
------------------------------------------------------------
PER-TARGET BREAKDOWN
------------------------------------------------------------
Amazon (moderate)
Success: 88.0% (35/40)
Median latency: 312ms
Cloudflare (protected)
Success: 82.5% (33/40)
Median latency: 445ms
Google Search (moderate)
Success: 92.5% (37/40)
Median latency: 289ms
example.com (unprotected)
Success: 100.0% (40/40)
Median latency: 142ms
httpbin (unprotected)
Success: 100.0% (40/40)
Median latency: 167ms
============================================================
Interpreting Results
Success Rate Analysis
| Success Rate | Assessment | Action |
|---|---|---|
| 95-100% | Excellent | Provider works well for this target |
| 85-95% | Good | Acceptable with retry logic |
| 70-85% | Marginal | Consider different proxy type or provider |
| <70% | Poor | Not suitable for this target |
Latency Analysis
Focus on median and P95, not average. Averages are skewed by outliers. The median tells you what most requests experience; the P95 tells you what your slowest requests look like:
- Median <300ms: Good for most use cases
- Median 300-500ms: Acceptable for scraping, slow for interactive use
- Median >500ms: Only suitable for background data collection
- P95 >3x median: Inconsistent proxy performance, investigate further
IP Diversity Analysis
For rotating residential proxies, check how many unique IPs appear in your benchmark:
- Unique IPs = total requests: True per-request rotation (ideal)
- Unique IPs > 80% of requests: Good rotation with some reuse
- Unique IPs < 50% of requests: Pool is small or rotation is sticky -- investigate
Advanced Benchmark: Sustained Load Test
async def sustained_load_test(
proxy_url: str,
duration_minutes: int = 30,
requests_per_minute: int = 60
):
"""Run a sustained load test over a longer period.
Measures how proxy performance degrades (or doesn't)
over time. Useful for detecting IP pool exhaustion
or rate limiting by the proxy provider.
"""
interval = 60 / requests_per_minute
end_time = time.monotonic() + (duration_minutes * 60)
minute_results = {} # minute_number -> list of results
current_minute = 0
connector = aiohttp.TCPConnector(limit=20, ssl=False)
async with aiohttp.ClientSession(connector=connector) as session:
while time.monotonic() < end_time:
minute = int((time.monotonic() - (end_time - duration_minutes * 60)) / 60)
if minute not in minute_results:
minute_results[minute] = []
target = TARGET_SITES[len(minute_results[minute]) % len(TARGET_SITES)]
result = await single_request(
session, target["url"], proxy_url, target["name"]
)
minute_results[minute].append(result)
await asyncio.sleep(interval)
# Report per-minute statistics
print("\nSUSTAINED LOAD TEST RESULTS")
print("-" * 60)
print(f"{'Minute':<8} {'Requests':<10} {'Success %':<12} {'Median ms':<12}")
print("-" * 60)
for minute in sorted(minute_results.keys()):
results = minute_results[minute]
successful = [r for r in results if r.status == 200]
latencies = [r.latency_ms for r in successful]
rate = len(successful) / len(results) * 100 if results else 0
median = statistics.median(latencies) if latencies else 0
print(f"{minute:<8} {len(results):<10} {rate:<12.1f} {median:<12.0f}")
return minute_results
Comparing Providers Side by Side
Run the benchmark against multiple providers and compare results. Key comparison points:
# Run against 3 providers
python proxy_benchmark.py --proxy-url "http://user:pass@provider1.com:8080" \
--requests 500 --output provider1.json
python proxy_benchmark.py --proxy-url "http://user:pass@gate.hexproxies.com:8080" \
--requests 500 --output hexproxies.json
python proxy_benchmark.py --proxy-url "http://user:pass@provider3.com:8080" \
--requests 500 --output provider3.json
# Compare results
python compare_benchmarks.py provider1.json hexproxies.json provider3.json
Comparison Script
#!/usr/bin/env python3
"""Compare benchmark results from multiple providers."""
import json
import sys
def load_summary(filepath):
"""Load benchmark summary from JSON file."""
with open(filepath) as f:
data = json.load(f)
return data["summary"]
def compare(files):
"""Print side-by-side comparison table."""
summaries = {}
for f in files:
name = f.replace(".json", "")
summaries[name] = load_summary(f)
# Header
names = list(summaries.keys())
header = f"{'Metric':<25}" + "".join(f"{n:<20}" for n in names)
print(header)
print("-" * len(header))
# Metrics
metrics = [
("Success Rate", "success_rate", "%"),
("Median Latency", "latency_median_ms", "ms"),
("P95 Latency", "latency_p95_ms", "ms"),
("Unique IPs", "unique_ips", ""),
("Throughput", "requests_per_second", "req/s"),
]
for label, key, unit in metrics:
row = f"{label:<25}"
for name in names:
val = summaries[name].get(key, "N/A")
row += f"{val}{unit:<{20 - len(str(val)) - len(unit)}}"
print(row)
if __name__ == "__main__":
if len(sys.argv) < 3:
print("Usage: python compare_benchmarks.py file1.json file2.json [file3.json ...]")
sys.exit(1)
compare(sys.argv[1:])
Common Benchmarking Mistakes
- Testing too few requests. 10 requests is not statistically significant. Use at least 200 requests per provider for reliable results. For production decisions, use 500-1,000.
- Testing only unprotected targets. Every proxy works against httpbin. Test against your actual target sites and similar protection levels.
- Ignoring P95 latency. A provider with great median latency but terrible P95 means 1 in 20 requests is unacceptably slow. This matters for user-facing applications.
- Not testing during peak hours. Proxy performance can vary by time of day due to shared infrastructure load. Run benchmarks during your actual usage hours.
- Comparing different proxy types. Comparing residential rotating proxies to ISP static proxies is meaningless -- they serve different purposes. Compare like for like.
Benchmark Checklist
| Step | Action | Why |
|---|---|---|
| 1 | Define target sites matching your use case | Results are only meaningful for your targets |
| 2 | Run 200+ requests per provider | Statistical significance |
| 3 | Test at multiple times of day | Detect time-dependent performance variations |
| 4 | Compare median AND P95 latency | Median alone hides tail latency issues |
| 5 | Check IP diversity (for rotating proxies) | Verify actual pool size vs marketing claims |
| 6 | Run sustained load test (30+ min) | Detect performance degradation over time |
| 7 | Calculate true cost per successful request | Failed requests waste budget |
Frequently Asked Questions
How many requests do I need for a reliable benchmark?
Minimum 200 requests per provider for basic comparison. For production decisions with confidence, use 500-1,000 requests spread across multiple time windows. The benchmark script above defaults to 200 for quick comparisons.
Should I test against my actual target sites?
Yes, absolutely. Protection levels vary dramatically between sites. A proxy that achieves 98% success on Google might achieve 75% on Amazon. Add your target URLs to the TARGET_SITES list in the benchmark script for the most relevant results.
How often should I re-benchmark proxy providers?
Quarterly for ongoing provider evaluation. Additionally, re-benchmark when: (1) you notice success rate drops in production, (2) your target sites update their anti-bot protection, or (3) your provider announces infrastructure changes.
What if my success rates are low with all providers?
Low success rates across all providers indicate the target's protection requires more than just proxy IPs. You may need: browser automation (Playwright/Puppeteer), TLS fingerprint impersonation, behavioral simulation, or a dedicated scraping API for those specific targets. See our guide on anti-bot detection in 2026 for details.
Can I benchmark Hex Proxies before purchasing?
Yes. Hex Proxies plans have no minimum commitment. Purchase the smallest available plan, run this benchmark against your targets, and evaluate the results before scaling up. The benchmark framework above works with any provider that supports HTTP proxy authentication.
Independent benchmarking is the only way to make informed proxy provider decisions. The scripts in this guide give you a standardized, reproducible framework for evaluating any provider. Test Hex Proxies residential ($1.70/GB) and ISP ($0.83/IP) against your actual targets using the benchmark above, and compare the results against any competing provider. View plans and start benchmarking.