Proxies That Work logo

Proxy Concurrency Limits Explained: Threads, Connections, and Requests per IP

By Jesse Lewis7/22/20265 min read
Proxy Concurrency Limits Explained: Threads, Connections, and Requests per IP

Proxy concurrency limits control how many requests your system can keep active at the same time. They are not the same as thread count, request rate, proxy pool size, or total monthly traffic.

For technical teams, the practical goal is not to maximize concurrency. It is to find the highest stable level that produces more successful responses without causing timeouts, rate limits, connection resets, or unnecessary retries.

A safe configuration usually combines three controls:

  • A global limit across the entire application
  • A per-domain limit for each target
  • A per-IP limit for each proxy

Understanding how threads, connections, and requests per IP relate makes it easier to scale without overwhelming your own infrastructure, the proxy service, or the target website.

What Does Proxy Concurrency Mean?

Proxy concurrency is the number of requests actively passing through a proxy system at one moment.

If 50 requests have been sent and your application is still waiting for their responses, your current concurrency is approximately 50. It does not matter whether those requests were launched by 50 operating system threads, 50 asynchronous tasks, or a smaller number of workers using connection pooling.

Several terms are often treated as interchangeable even though they measure different things.

Term What it measures Why it matters
Threads Application execution units Threads can create work, but do not directly determine network concurrency
Async tasks Scheduled units in an event loop Many tasks can run without creating one operating system thread per request
Connections Open TCP or TLS transport sessions Connections consume client, proxy, and server resources
In-flight requests Requests sent but not yet completed This is the clearest measure of active concurrency
Requests per second Completed or attempted requests over time This measures throughput, not concurrency
Requests per IP Traffic assigned to each proxy address This affects per-IP load and target-side rate limits
Proxy pool size Number of available IP addresses A larger pool provides capacity but does not set a safe request rate

A crawler can have 1,000 proxies but only 20 concurrent requests. Another system might have 20 proxies and attempt 500 concurrent requests. The second system has a smaller pool but much higher per-IP pressure.

Threads, Connections, and Requests Are Not the Same

Threads Create Work

A thread is a unit of execution inside an application. Traditional synchronous programs may assign one request to each worker thread, but that is only one programming model.

An asynchronous client can manage hundreds of requests with one event-loop thread. In that case, increasing the number of tasks does not necessarily increase the number of operating system threads.

This is why a setting such as workers=100 or threads=100 does not tell you the actual proxy concurrency. You must also inspect:

  • The HTTP client’s connection pool
  • The number of pending tasks
  • Per-host limits
  • Proxy-specific semaphores
  • Request timeouts
  • Retry behavior

Our comparison of Requests, HTTPX, and AIOHTTP for scraping explains how different Python clients handle synchronous and asynchronous workloads.

Connections Carry Requests

An HTTP connection is the transport path between two endpoints. When a proxy is involved, your client first connects to the proxy, which then connects or tunnels traffic to the destination.

HTTP/1.1 supports persistent connections, so multiple requests can reuse an established connection instead of performing a new connection setup every time. Clients should still remain conservative about opening many parallel connections because each connection consumes resources, and excessive connection counts can contribute to congestion or be treated as abusive traffic.

HTTP/2 works differently. It can carry multiple concurrent streams through one connection. A server or intermediary can advertise a maximum number of active streams through SETTINGS_MAX_CONCURRENT_STREAMS, which means one HTTP/2 connection may support several simultaneous requests without opening a separate connection for each one.

The practical lesson is simple:

Ten concurrent requests do not always require ten connections, and ten open connections do not always mean ten requests are active.

Requests per IP Measure Exit-Point Pressure

Requests per IP describe how much traffic each proxy address carries.

Suppose a crawler runs 100 concurrent requests through 20 proxies. If work is evenly distributed, each proxy handles about five active requests:

Per-IP concurrency = Global concurrency ÷ Active proxy count
100 ÷ 20 = 5 concurrent requests per IP

This is only an average. Poor rotation can assign 20 requests to one proxy while leaving another proxy idle.

For large workloads, traffic distribution matters as much as total pool size. Bulk proxies for large-scale web scraping are useful because they let teams spread active work across a wider set of IPs instead of concentrating requests on a few endpoints.

Concurrency Is Not the Same as Throughput

Concurrency measures active work. Throughput measures completed work over time.

A rough planning formula is:

Expected throughput ≈ Concurrency ÷ Average response time

If average response time is two seconds and the system maintains 40 requests in flight:

40 ÷ 2 seconds = approximately 20 requests per second

This estimate assumes the workload remains stable. Real throughput will be lower when requests fail, connections wait in queues, retries occur, or response times vary.

Increasing concurrency can improve throughput until one part of the system becomes a bottleneck. After that point, adding more requests usually increases waiting time rather than completed output.

Common bottlenecks include:

  • Client CPU or memory
  • Available file descriptors
  • Connection pool capacity
  • Proxy account limits
  • Per-IP network capacity
  • Target response time
  • Target rate limiting
  • Database or queue throughput
  • Retry volume

The correct objective is therefore not maximum requests in flight. It is maximum successful throughput at an acceptable latency and error rate.

Where Proxy Concurrency Limits Come From

There is rarely one universal proxy concurrency limit. Your effective limit is usually the lowest capacity across several layers.

1. Application Limits

Your code may restrict active tasks through:

  • Worker counts
  • Thread pools
  • Async semaphores
  • Queue consumers
  • Browser tabs or contexts
  • Connection-pool settings

A semaphore set to 50 means no more than 50 protected operations can run concurrently, even if 5,000 jobs are waiting in the queue.

2. HTTP Client Limits

HTTP libraries maintain connection pools to avoid creating a new transport connection for every request.

A client may have separate settings for:

  • Total connections
  • Connections per host
  • Idle connections
  • Keep-alive duration
  • Connection acquisition timeout
  • HTTP/2 streams

When tasks exceed the available connection pool, requests wait for a free connection. This waiting time is easy to misdiagnose as proxy latency.

3. Proxy Network Limits

A proxy provider may apply limits by:

  • Account
  • Subscription
  • Gateway
  • Proxy endpoint
  • Individual IP
  • Simultaneous TCP connection
  • Bandwidth
  • Request rate

These limits are provider-specific. Never assume that buying 1,000 IPs automatically permits 1,000 simultaneous connections.

Ask the provider whether the plan includes account-level concurrency restrictions, per-IP connection limits, idle timeouts, or fair-use conditions.

4. Target-Side Limits

A destination website can apply controls based on:

  • Source IP
  • Account or session
  • URL path
  • API key
  • Request pattern
  • Geographic region
  • Browser fingerprint
  • Overall server load

A proxy network may technically support 500 simultaneous requests, while the target begins returning 429 responses when one IP sends more than a small number of requests in a short window.

The target’s tolerance is often the real limit.

5. Your Own Infrastructure

The crawler itself can fail before either the proxy or the target reaches capacity.

Watch for:

  • CPU saturation
  • Memory growth
  • Exhausted sockets
  • Event-loop lag
  • Queue buildup
  • Database contention
  • Slow disk writes
  • Logging overhead

Scaling proxy concurrency safely requires end-to-end measurement, not just proxy monitoring.

How to Calculate a Starting Concurrency Limit

There is no safe universal number of requests per proxy IP. A practical starting point can be calculated from expected throughput and measured latency.

Step 1: Set a Throughput Goal

Suppose a crawler needs to complete 30 successful requests per second.

Step 2: Measure Response Time

Run a small test and record average and percentile latency. Assume the average completed request takes 1.5 seconds.

Step 3: Estimate Required Concurrency

Required concurrency =
Target requests per second × Average response time
30 × 1.5 = 45 concurrent requests

Add a modest buffer for natural latency variation, but do not double concurrency without evidence. A starting global limit of 50 may be reasonable for a controlled test.

Step 4: Divide Work Across the Pool

If 25 proxies are active:

50 concurrent requests ÷ 25 proxies = 2 requests per IP

Set the per-IP cap at two and observe the result.

Step 5: Measure Successful Output

Track:

  • Successful responses per second
  • P50, P95, and P99 latency
  • Timeout rate
  • 403 and 429 frequency
  • Connection errors
  • Retries per completed request
  • CPU and memory utilization

A production proxy system should be evaluated through these metrics rather than raw request count. The guide to monitoring proxy performance in production provides a broader monitoring framework.

Example Concurrency Configurations

The following numbers are starting examples, not provider or target guarantees.

Workload Global concurrency Active proxies Approximate per-IP concurrency
Small catalog check 10 10 1
Recurring SEO monitoring 30 30 1
Public directory crawl 100 50 2
Large distributed crawl 500 250 2
Browser-based QA 20 browser tasks 20 1

Browser automation usually needs lower concurrency than raw HTTP requests because each browser context consumes more CPU, memory, bandwidth, and connection resources.

A system using Playwright should therefore not copy concurrency settings directly from a lightweight AIOHTTP crawler.

Controlling Global and Per-Proxy Concurrency in Python

The following AIOHTTP example applies one global semaphore and one semaphore per proxy.

import asyncio
from collections.abc import Sequence

import aiohttp


GLOBAL_CONCURRENCY = 50
PER_PROXY_CONCURRENCY = 2
REQUEST_TIMEOUT_SECONDS = 20

PROXIES = [
    "http://PROXY_USER:PROXY_PASSWORD@PROXY_HOST_1:PROXY_PORT",
    "http://PROXY_USER:PROXY_PASSWORD@PROXY_HOST_2:PROXY_PORT",
]


async def fetch(
    session: aiohttp.ClientSession,
    url: str,
    proxy: str,
    global_limit: asyncio.Semaphore,
    proxy_limit: asyncio.Semaphore,
) -> tuple[str, int | None]:
    async with global_limit, proxy_limit:
        try:
            async with session.get(proxy=proxy, url=url) as response:
                await response.read()
                return url, response.status
        except (aiohttp.ClientError, asyncio.TimeoutError):
            return url, None


async def crawl(urls: Sequence[str]) -> list[tuple[str, int | None]]:
    global_limit = asyncio.Semaphore(GLOBAL_CONCURRENCY)
    proxy_limits = {
        proxy: asyncio.Semaphore(PER_PROXY_CONCURRENCY)
        for proxy in PROXIES
    }

    timeout = aiohttp.ClientTimeout(total=REQUEST_TIMEOUT_SECONDS)
    connector = aiohttp.TCPConnector(
        limit=GLOBAL_CONCURRENCY,
        limit_per_host=GLOBAL_CONCURRENCY,
    )

    async with aiohttp.ClientSession(
        connector=connector,
        timeout=timeout,
    ) as session:
        tasks = []

        for index, url in enumerate(urls):
            proxy = PROXIES[index % len(PROXIES)]
            tasks.append(
                fetch(
                    session=session,
                    url=url,
                    proxy=proxy,
                    global_limit=global_limit,
                    proxy_limit=proxy_limits[proxy],
                )
            )

        return await asyncio.gather(*tasks)


if __name__ == "__main__":
    sample_urls = [
        "https://example.com/page-1",
        "https://example.com/page-2",
    ]

    results = asyncio.run(crawl(sample_urls))
    print(results)

The global semaphore prevents the crawler from opening unlimited work. The per-proxy semaphore prevents one proxy from receiving more than two simultaneous requests.

Production systems should also add domain-specific limits, structured retries, response validation, and centralized metrics.

How to Find the Highest Stable Concurrency

Use incremental load testing instead of choosing a large number immediately.

  1. Start with a low global limit.
  2. Hold the proxy pool and target set constant.
  3. Run long enough to observe normal latency variation.
  4. Increase concurrency in small steps.
  5. Compare successful throughput, not attempted requests.
  6. Stop when throughput flattens or error rates rise materially.
  7. Reduce the limit to leave operational headroom.

For example:

Test Concurrency Successful requests/sec Error rate P95 latency
A 25 15 1% 1.8 sec
B 50 28 2% 2.1 sec
C 75 31 8% 4.7 sec
D 100 30 15% 8.4 sec

Test B is likely the better operating point. Test C produces only a small throughput improvement but sharply increases failures and latency.

When failures appear, do not automatically add more retries. Poor retry controls can multiply the original load. Use status-aware backoff and retry budgets such as those described in retry strategies for 429, 403, and 5xx responses.

Signs Your Proxy Concurrency Is Too High

Rising 429 Responses

A spike in 429 responses often indicates that the target is receiving requests faster than it accepts them. Lower per-domain or per-IP concurrency before expanding retries.

Latency Increases Faster Than Throughput

When concurrency increases by 50 percent but successful throughput barely moves, requests are probably waiting on a bottleneck.

Connection Resets and Timeouts Increase

Excessive open connections can exhaust resources in the client, proxy, or destination. HTTP specifications explicitly caution clients to limit simultaneous connections and note that excessive connection behavior may be considered abusive.

One Proxy Handles Disproportionate Traffic

Round-robin assignment does not always produce balanced active load. Slow requests can keep some proxies occupied longer, while fast proxies receive more new work.

Track active requests and completed requests per proxy, not only assignment count.

Retry Volume Grows

If each original request produces two or three retry attempts, apparent concurrency may understate the actual pressure placed on the proxy pool and target.

Common Concurrency Mistakes

Setting One Thread per Proxy

One thread per proxy is not a reliable design rule. A proxy might safely handle more than one active request, or the target may require less than one sustained request per IP.

Measure behavior instead of tying two unrelated settings together.

Treating Pool Size as Permission to Send Faster

A 1,000-IP pool provides distribution capacity. It does not guarantee that the client, proxy provider, or destination can sustain 1,000 simultaneous requests.

Using One Limit for Every Domain

Different targets have different response times and traffic policies. Maintain separate per-domain queues and concurrency settings.

Ignoring Connection Reuse

Creating a fresh connection for every request adds setup overhead and can increase socket pressure. Configure connection pooling and keep-alive behavior deliberately.

Increasing Concurrency and Retries Together

This makes diagnosis difficult and can produce a request storm. Change one variable at a time.

Skipping Backpressure

When producers add jobs faster than workers complete them, queues grow indefinitely. Use bounded queues so the crawl slows down when downstream systems cannot keep up.

Teams managing hundreds or thousands of IPs should also organize health data and load distribution carefully. See the practical guide to managing large proxy lists for pool-level controls.

Questions to Ask a Proxy Provider

Before setting production concurrency, confirm:

  • Is there an account-level concurrency cap?
  • Is there a simultaneous connection limit per proxy IP?
  • Are connections limited by port, endpoint, or subscription?
  • Does the service support HTTP and HTTPS tunneling?
  • Are idle connections closed after a fixed period?
  • Are there bandwidth or fair-use limits?
  • Can one proxy be used by several workers?
  • Does authentication support distributed servers?
  • Are failed proxies replaced?
  • How are planned maintenance and network incidents communicated?

Do not rely on “unlimited” without reading what the term covers. Unlimited bandwidth, unlimited requests, and unlimited simultaneous connections are different claims.

Frequently Asked Questions

What Is a Good Proxy Concurrency Limit?

A good limit is the highest tested level that maintains stable success rates, acceptable latency, and responsible per-IP traffic. There is no universal number because target behavior, response time, proxy type, and application architecture differ.

How Many Concurrent Requests Can One Proxy Handle?

The technical capacity may be higher than the safe target-facing rate. Start with a low per-IP limit, often one active request for sensitive workloads, and increase only after measuring results.

Does One Thread Equal One Proxy Connection?

No. A thread can make several sequential connections, while an asynchronous event loop can manage many connections with one thread.

Does HTTP/2 Remove Concurrency Limits?

No. HTTP/2 multiplexes several streams over one connection, but endpoints can still limit active streams, flow control, and resource use.

Should I Increase Proxy Count or Concurrency First?

Increase the proxy pool when per-IP pressure is already high. Increase concurrency when proxies are underused and the rest of the system has measured capacity.

Can More Concurrency Make a Crawl Slower?

Yes. Once a bottleneck is saturated, added concurrency creates longer queues, more timeouts, and additional retries without improving successful throughput.

Scale Successful Throughput, Not Active Requests

Proxy concurrency limits should protect three things at once: your application, the proxy infrastructure, and the target website.

Start with measured latency and a clear throughput goal. Set global, domain-level, and per-IP limits. Increase them gradually while tracking successful responses, latency percentiles, retries, and error classifications.

For workloads that benefit from distributing controlled traffic across many HTTP/HTTPS IPs, compare the available bulk datacenter proxy plans. ProxiesThatWork offers per-IP plans with HTTP/HTTPS support and IP authentication, making it possible to expand pool capacity separately from application concurrency.

About the Author

J

Jesse Lewis

Jesse Lewis is a researcher and content contributor for ProxiesThatWork, covering compliance trends, data governance, and the evolving relationship between AI and proxy technologies. He focuses on helping businesses stay compliant while deploying efficient, scalable data-collection pipelines.

Proxies That Work logo
© 2026 ProxiesThatWork LLC. All Rights Reserved.