Using Proxies with aiohttp: The Four Things That Fail Quietly
aiohttp handles proxies differently from requests, and the differences fail silently: SOCKS5 connectors, timeouts, and pooling that breaks rotation.
Loading page content.
Rotation is not a quality setting you turn up. It is a choice about whether the target expects one visitor or many, and picking wrong is the most expensive misconfiguration in proxy work.
Dana Whitfield
· updated 23 Aug 2026
The single most common configuration mistake we see is a customer running a login flow through per-request rotation, concluding the proxies are "unstable", and buying a larger pool that makes it worse.
Rotation is not a dial from worse to better. It is a statement about what the target should believe. Per-request rotation says "many unrelated visitors". A sticky session says "one visitor, doing a sequence of things". Pick the one that matches reality for that flow.
| Your flow | Mode | Why |
|---|---|---|
| Public product/list pages, no cookies | Per-request | Maximum throughput, no state to break |
| Search results with cookie-carried filters | Sticky, short | Pagination is stateful |
| Any authenticated session | Sticky, whole journey | An address change mid-session reads as hijacking |
| Cart and checkout | Sticky, whole journey | Fraud scoring weights address continuity heavily |
| Account creation and warm-up | Static IP, permanent | The address is part of the account's identity |
| Ad verification from a given market | Per-request within one country | You want many independent samples |
| Uptime or latency probing | Sticky per probe | Otherwise you measure the pool, not the target |
The pattern is simple: state on the target implies stickiness on your side. If the origin is maintaining anything about you between requests, your address should not change beneath it.
A session is a segment in the proxy username. The same string maps to the same exit IP for as long as the lease holds; a different string gets a different address. There is no separate API call and no connection to keep open.
# Two independent journeys, two exit IPs, same credential
curl -x http://user-country-us-session-7f2a91c40b3e:[email protected]:8080 \
-s https://api.fleetproxy.com/v1/ip
curl -x http://user-country-us-session-c40b3e7f2a91:[email protected]:8080 \
-s https://api.fleetproxy.com/v1/ipBecause the session lives in the credential string, it composes with targeting. user-country-gb-city-london-session-abc123def456 is a London address held for the session. Omit the session segment entirely and you get per-request rotation.
Leases last up to 30 minutes of activity and are released after roughly 10 minutes of silence. Neither is a guarantee: the underlying peer can leave the network at any moment, which is inherent to residential inventory rather than a policy choice. Treat the session as best-effort and write code that notices when the address changed.
import httpx
class StickySession:
"""Wraps one journey and detects when the underlying exit IP has moved."""
def __init__(self, session_id: str, country: str = "us") -> None:
proxy = (
f"http://user-country-{country}-session-{session_id}"
f":[email protected]:8080"
)
self._client = httpx.Client(proxy=proxy, timeout=30.0, follow_redirects=True)
self._ip: str | None = None
def get(self, url: str) -> httpx.Response:
response = self._client.get(url)
current = response.headers.get("x-fleet-exit-ip")
if self._ip is None:
self._ip = current
elif current and current != self._ip:
raise ConnectionError(f"exit IP moved {self._ip} -> {current}; restart journey")
return response
def close(self) -> None:
self._client.close()Raising is deliberate. Silently continuing after the address moved produces a half-authenticated session that returns plausible-looking wrong data — the worst failure mode available.
Per-request rotation, used where state exists. Symptoms: random logouts, carts emptying, CSRF token mismatches, pagination that restarts at page one, challenge pages that appear only on longer runs. Every one of these gets misdiagnosed as flaky proxies.
Sticky sessions, held too long. One session used for a hundred thousand requests is one IP doing a hundred thousand requests. You have reinvented the single-address problem with extra steps. One session per logical journey, then discard.
Too many sessions at once. Two hundred concurrent sticky sessions on one target from one account is a visible cluster even though every address differs. Concurrency budgets still apply.
Session reuse across identities. Two accounts sharing one session string are two accounts sharing one IP. If the point of the session was isolation, that single line of code undoes it.
Match the session's life to the journey's, not to the lease's maximum:
Static residential exists for exactly the case where 30 minutes is nowhere near long enough. If a profile needs the same address next Tuesday, a rotating pool with sticky sessions is the wrong product.
Ask what the origin thinks it is looking at. If it thinks it is looking at one person doing one task, hold the address. If it does not know or care that a previous request happened, rotate freely. Almost every rotation question answers itself once phrased that way.
aiohttp handles proxies differently from requests, and the differences fail silently: SOCKS5 connectors, timeouts, and pooling that breaks rotation.
A mobile exit is shared with thousands of real subscribers, which is what makes it hard to block. How dedicated LTE ports differ from pooled bandwidth.
A 407 means the proxy refused your credentials, and there are exactly six reasons it does that. Here is how to tell them apart in under a minute.
Every snippet in this article points at the production gateway. Create an account, take the 50 MB residential trial, and swap in your credentials.
No card required for the trial. Cancel or downgrade at any time.