v1.8.91-d84675c
← Back to Hex Proxies

Proxies for API Load Testing

Last updated: April 2026

By Hex Proxies Engineering Team

Learn how to use proxy infrastructure to create realistic API load tests that simulate distributed user traffic, avoid rate limiting artifacts, and produce accurate performance metrics.

intermediate20 minutesdeveloper-qa

Prerequisites

  • Python or Node.js
  • Familiarity with load testing concepts
  • Hex Proxies ISP or residential plan

Steps

1

Set up proxy credentials

Configure Hex Proxies credentials with sticky sessions for consistent per-worker IP assignment during the test.

2

Build the distributed load tester

Implement an async load test runner that distributes requests across multiple proxy IPs.

3

Run baseline comparison

Execute the same test from a single IP and through proxies. Compare latency, error rates, and throughput.

4

Analyze rate limiter behavior

Check whether your rate limiter treats distributed traffic differently and adjust limits if needed.

5

Document findings

Record the performance differences and use proxied results as the true performance baseline.

Proxies for Realistic API Load Testing

Load testing from a single IP produces misleading results. Your application's rate limiter, CDN, and WAF all treat single-IP traffic differently than distributed traffic from real users. Proxy infrastructure creates realistic test conditions by distributing load across many source IPs.

Why Single-IP Load Tests Fail

When you run `hey` or `wrk` from one server, several things happen that do not reflect production reality:

  1. **Rate limiters activate**: Your app blocks the test IP after X requests, producing artificial 429 errors.
  2. **CDN caching skews results**: CDNs cache responses per edge node. A single-IP test hits one edge node, inflating cache hit rates.
  3. **WAF interference**: Web application firewalls flag rapid requests from one IP as an attack, returning 403s.
  4. **Connection pooling artifacts**: HTTP/2 multiplexing over a single connection behaves differently than many separate connections.

Distributed Load Test Architecture

Load Test Runner
    ├── Worker 1 → Proxy IP A → Your API
    ├── Worker 2 → Proxy IP B → Your API
    ├── Worker 3 → Proxy IP C → Your API
    └── Worker N → Proxy IP N → Your API

Python Load Test with Proxy Distribution

import asyncio
import aiohttp
import time
from dataclasses import dataclass

@dataclass(frozen=True) class LoadTestResult: url: str status: int latency_ms: float proxy_session: str

@dataclass(frozen=True) class LoadTestSummary: total_requests: int successful: int failed: int avg_latency_ms: float p95_latency_ms: float p99_latency_ms: float requests_per_second: float

class ProxiedLoadTester: def __init__(self, username: str, password: str, concurrency: int = 50): self._username = username self._password = password self._concurrency = concurrency

def _get_proxy(self, worker_id: int) -> str: session = f"loadtest-{worker_id}" return f"http://{self._username}-session-{session}:{self._password}@gate.hexproxies.com:8080"

async def run(self, url: str, total_requests: int) -> LoadTestSummary: semaphore = asyncio.Semaphore(self._concurrency) results: list[LoadTestResult] = [] start_time = time.monotonic()

async with aiohttp.ClientSession() as session: tasks = [] for i in range(total_requests): proxy = self._get_proxy(i % self._concurrency) tasks.append(self._send_request(session, url, proxy, f"w{i}", semaphore)) results = await asyncio.gather(*tasks, return_exceptions=True) valid_results = [r for r in results if isinstance(r, LoadTestResult)]

elapsed = time.monotonic() - start_time latencies = sorted([r.latency_ms for r in valid_results]) successful = sum(1 for r in valid_results if 200 <= r.status < 400)

return LoadTestSummary( total_requests=total_requests, successful=successful, failed=total_requests - successful, avg_latency_ms=sum(latencies) / max(len(latencies), 1), p95_latency_ms=latencies[int(len(latencies) * 0.95)] if latencies else 0, p99_latency_ms=latencies[int(len(latencies) * 0.99)] if latencies else 0, requests_per_second=total_requests / max(elapsed, 0.001), )

async def _send_request( self, session: aiohttp.ClientSession, url: str, proxy: str, session_id: str, semaphore: asyncio.Semaphore ) -> LoadTestResult: async with semaphore: start = time.monotonic() try: async with session.get(url, proxy=proxy, timeout=aiohttp.ClientTimeout(total=30)) as resp: await resp.read() latency = (time.monotonic() - start) * 1000 return LoadTestResult(url=url, status=resp.status, latency_ms=latency, proxy_session=session_id) except Exception: latency = (time.monotonic() - start) * 1000 return LoadTestResult(url=url, status=0, latency_ms=latency, proxy_session=session_id) ```

Usage Example

async def main():
    tester = ProxiedLoadTester(
        username="YOUR_USER",
        password="YOUR_PASS",
        concurrency=100,
    )
    summary = tester.run("https://api.yourapp.com/v1/health", total_requests=10000)
    result = await summary
    print(f"RPS: {result.requests_per_second:.1f}")
    print(f"P95 Latency: {result.p95_latency_ms:.1f}ms")

asyncio.run(main()) ```

Interpreting Results

Compare proxied load test results against single-IP tests. The differences reveal how much your rate limiting, CDN, and WAF affect real user experience. Proxied tests give you the true performance profile your users experience.

With Hex Proxies' ISP infrastructure delivering sub-50ms proxy latency, the overhead added by the proxy layer is minimal — typically 10-30ms — making your load test results accurate representations of production conditions.

Tips

  • *Use sticky sessions per worker thread so each concurrent user has a consistent IP.
  • *Add 10-30ms to latency measurements to account for proxy overhead when comparing against direct tests.
  • *Test your rate limiter with distributed IPs to verify it handles real-world traffic patterns correctly.
  • *ISP proxies add less latency than residential — prefer them for load testing accuracy.
  • *Run tests during off-peak hours to isolate proxy performance from network congestion.

Ready to Get Started?

Put this guide into practice with Hex Proxies.

Cookie Preferences

We use cookies to ensure the best experience. You can customize your preferences below. Learn more