When a load test hits a rate limit, WAF, or geographic load balancing, the first thought is, "I'll run traffic from different IPs." It sounds logical, but in practice, proxies in k6 and JMeter solve a narrow set of tasks, and the rest still needs to be fixed by the test architecture. In this article, we will discuss how to technically connect proxies to both tools, which types of IPs are suitable for this, and where the proxy server's responsibility ends.
Why Load Testing Needs Different IPs
By default, k6 and JMeter send all traffic from a single IP address — the server where the test is running (or a cloud machine in CI/CD). This is fine for 90% of scenarios, but in a few cases, a single IP is not enough:
- Rate-limiting at the IP level. Many APIs and load balancers (nginx limit_req, AWS WAF, Cloudflare) limit the number of requests from one address per second. Real users come from thousands of different IPs, while your test comes from one — and hits the limit before reaching the actual bottleneck of the application.
- Testing geo-dependent logic. CDNs, targeted content, regional pricing, geo-blocking — all of this needs to be checked with IPs from specific countries, not from the datacenter where your test runner is physically located.
- Checking the load balancer and anycast network. If the infrastructure distributes traffic across regions based on the client's IP, a test from one address will only check one route, not the entire network.
- Simulating real traffic distribution. Production traffic IPs are distributed across providers, subnets, and sometimes mobile operators — this affects the behavior of CDNs, caches, and edge logic.
It is important to delineate immediately: if you are testing the performance of the backend (how many RPS your API can handle), proxies are almost never needed — what matters is the clean load, not the diversity of sources. Proxies are needed when the test scenario depends specifically on the client's IP address.
Which Proxies Are Suitable: Datacenter, Residential, Mobile
The choice of proxy type directly affects what you are testing — the throughput of the infrastructure or the behavior of the system under "live" IPs.
| Proxy Type | Latency and Speed | When to Use in Load Tests |
|---|---|---|
| Datacenter Proxies | Low latency, high throughput | Checking rate limits, bypassing "N requests from IP" limits, generating a large volume of RPS from different subnets |
| Residential Proxies | Higher latency, speed limited by the real user's connection | Testing geo-logic, checking CDNs and anti-bot systems that trust residential ranges more than datacenter ones |
| Mobile Proxies | Highest latency, unstable speed | Niche scenarios: testing mobile APIs, behavior during frequent IP changes (operators dynamically change the client's address) |
For pure RPS and rate limit checks, datacenter proxies are almost always sufficient — they provide stable low latency and do not distort test results with external network instability. Residential and mobile IPs add realism to geo-tests but introduce their own latency, which must be accounted for when analyzing metrics — otherwise, it is easy to confuse "the proxy network is slow" with "your server is slow."
Setting Up Proxies in k6
k6 is a tool based on JavaScript (the engine is written in Go, scripts are written in JS), and by default, there is no built-in support for HTTP proxies through script options: proxies in k6 are set via environment variables or system settings, because under the hood, k6 uses the Go HTTP client.
The simplest way is using environment variables:
export HTTP_PROXY="http://login:password@proxy-host:port"
export HTTPS_PROXY="http://login:password@proxy-host:port"
k6 run script.js
This method will route all test traffic through a single proxy — suitable if you just need to leave the server's "white" IP. But if the task is to distribute virtual users (VU) across different IPs, a list of proxies and logic for selection at the script level is needed. k6 does not support per-VU proxies "out of the box," so a common workaround is to run several k6 processes in parallel, each with its own environment variable and its proxy from the pool:
// run-with-proxies.sh
PROXIES=("proxy1:port" "proxy2:port" "proxy3:port")
for proxy in "${PROXIES[@]}"; do
HTTP_PROXY="http://user:pass@$proxy" \
HTTPS_PROXY="http://user:pass@$proxy" \
k6 run --vus 50 --duration 5m script.js &
done
wait
This approach scales well in conjunction with k6 Cloud or orchestration through Kubernetes/Docker Compose — each container is launched with its own proxy variable. If IP rotation is needed within a single script run, it makes more sense to take proxies with rotation on the proxy provider's side (sticky session with TTL), rather than creating rotation within the JS code.
Setting Up Proxies in JMeter
In JMeter, proxies are set up natively, without environment variables, through the HTTP Request Defaults element or directly in the HTTP Request Sampler settings:
- Add the HTTP Request Defaults configuration element to the Thread Group.
- In the Proxy Server fields, specify the host and port of the proxy.
- If the proxy requires authentication — add the HTTP Authorization Manager element with the username and password for that host.
- All HTTP Request Samplers within the Thread Group will automatically inherit the proxy settings.
To distribute the load across multiple proxies in JMeter, it is convenient to use CSV Data Set Config — a file with a list of proxies is substituted into the host and port variables for each thread:
proxy_host,proxy_port,proxy_user,proxy_pass
proxy1.example.com,8000,user1,pass1
proxy2.example.com,8000,user2,pass2
proxy3.example.com,8000,user3,pass3
Connect the CSV Data Set Config to the Thread Group, specify the variables ${proxy_host} and ${proxy_port} in the Proxy Server/Proxy Port fields of the sampler — each JMeter thread will take its line from the file and go through its IP. To run from the command line (headless mode, which is recommended for real load runs), proxies can also be passed as flags:
jmeter -n -t test_plan.jmx -l results.jtl \
-H proxy-host -P port \
-u username -a password
This method sets a single common proxy for the entire test — for multi-proxy scenarios, you will still need to use CSV Data Set Config or Distributed Testing (multiple JMeter slaves, each with its own proxy in the config).
IP Rotation and Load Distribution
There are two fundamentally different approaches to distributing IPs in a load test:
- Static IP assignment to thread/VU. Each virtual user receives the same IP for the entire session — simulating a real user with one address. This is configured through sticky-session proxies or simply a fixed list in CSV/environment variables.
- IP rotation for each request. The proxy changes the outgoing address for each HTTP request — useful for testing rate-limit protection, but poorly reflects the real behavior of users, whose IPs do not change every second.
For a realistic load test, in 90% of cases, it is more correct to assign an IP to a VU/thread for the entire session — this way, the test honestly reflects the behavior of many independent users, rather than creating artificial noise. Rotation for each request makes sense to include only in narrow scenarios — for example, when specifically checking the triggering of rate-limit rules.
What Proxies Will Not Replace
Here is the key point of the article: proxies solve the problem of "from which IP the request comes," but do not solve the problems that load testing actually checks.
- The throughput of your test runner. If the load generator (the machine with k6/JMeter) hits CPU, memory, or network card limits — proxies will not fix this. Proxies add an extra network hop and their own latency, rather than relieving the load from the runner.
- The actual throughput of the server being tested. The server either withstands the load or it does not — the client's IP address does not affect this, except in cases of explicit rate limits by IP.
- The reliability of response time metrics. Proxies add their own latency (especially residential and mobile), which will appear in your reports as part of the response time. If the proxy latency is not subtracted separately, the final latency report will be distorted and unsuitable for comparison with production metrics.
- Scaling infrastructure for real traffic. A load test with proxies shows behavior under a specific pattern of requests but does not replace capacity planning, auto-scaling, and load profiles built on real production logs.
- Checking the correctness of business logic under load. Race conditions, database locks, queue degradation — these are found in concurrency tests and profiling, not by changing the outgoing IP.
Proxies are a tool for the specific task of "emulating traffic from different IPs," not a universal amplifier for load testing. If the goal is to understand how many RPS the backend can handle, proxies are generally not needed: a few test runners without proxies, with properly configured keep-alive and connection pooling, are sufficient.
Common Mistakes When Testing Through Proxies
In practice, most problems with proxies in load tests boil down to several recurring mistakes.
- Mixing proxy latency with the latency of the tested service. Without a baseline measurement (direct request without a proxy), it is impossible to understand how many milliseconds the proxy server itself adds.
- Using mobile or residential proxies where pure throughput is needed. Such proxies are limited by the real connection of the end device and physically will not withstand tens of thousands of RPS — for pure load, datacenter proxies are needed.
- Insufficient pool of IPs for a large number of VUs. If there are 10 proxy addresses for 5000 virtual users, the test will still hit the same rate limit, just with a delay — the service will quickly see the pattern "10 IPs, thousands of requests."
- Ignoring timeouts on the proxy side. Under high concurrent load, the proxy server may start rejecting connections itself — it is important to clarify the limits of simultaneous sessions with the proxy provider in advance.
- Running the test without checking whitelisting/authentication. If the proxy requires IP authentication, and the test is run from CI/CD with a dynamic runner address — requests will fail with 407 Proxy Authentication Required, and the test results will be meaningless.
Checklist Before Running the Test
- Determined whether the task truly requires different IPs or if a test from a single address is sufficient.
- Selected the type of proxy for the task: datacenter for RPS and rate limit, residential/mobile for geo-logic.
- Performed a baseline measurement without proxies to separate network latency from proxy latency.
- Checked the limits of simultaneous connections with the proxy provider for the required level of VUs.
- Configured proxy authentication (username/password or whitelisting the runner's IP).
- Decided whether a sticky session is needed for VUs or rotation for each request.
- Ran a test over a short interval (1-2 minutes) before the full run to check the configuration.
Conclusion
Proxies in k6 and JMeter are a working way to emulate traffic from different IP addresses, check the behavior of rate limits, and geo-dependent logic. But they are not a replacement for proper load testing planning: proxies do not check or improve server throughput, backend stability under concurrent load, or real capacity planning. Use them selectively — where the test truly depends on the client's IP address, not as a universal tool "for fairness."
If the task is to check IP limits and simulate mass traffic with high throughput, it makes sense to start with datacenter proxies — they provide minimal latency and can handle a large number of simultaneous sessions. For tests tied to geo-logic and trust of anti-bot systems regarding traffic origin, residential proxies are better suited, and for niche scenarios with mobile traffic — mobile IPs.