An aiohttp proxy is passed per request: session.get(url, proxy="http://USERNAME:PASSWORD@HOST:PORT"). Every call can use a different proxy from the same session, which makes aiohttp the easiest Python client for rotating IPs under asyncio. HTTPS targets are tunnelled with CONNECT, and SOCKS5 needs the separate aiohttp-socks package.
async with aiohttp.ClientSession() as session:
async with session.get("https://api.ipify.org?format=json", proxy="http://USERNAME:PASSWORD@HOST:PORT") as resp:
print(await resp.json())
Every snippet on this page ran on 2026-09-30 with aiohttp 3.14.3 and aiohttp-socks 0.12.0 on Python 3.12, against two local authenticating HTTP proxies and a SOCKS5 proxy that enforces username and password.
Before you start: copy your proxy details
- Open your order in the dashboard at https://app.proxyhive.io.
- Copy HOST, PORT, USERNAME and PASSWORD for each IP. Every ISP or datacenter IP is its own endpoint, with separate HTTP, HTTPS and SOCKS5 ports on the order. Use the HTTP port for
http://proxy URLs. - For rotation, put the full proxy URLs in one variable:
export PROXY_URLS="http://USERNAME:PASSWORD@HOST1:PORT,http://USERNAME:PASSWORD@HOST2:PORT"
Async crawlers are where a handful of datacenter proxies pays off: several static IPs, each its own endpoint, shared across hundreds of coroutines.
aiohttp proxy setup with authentication
import asyncio
import os
import aiohttp
PROXY = os.environ["PROXY_URLS"].split(",")[0]
async def main():
timeout = aiohttp.ClientTimeout(total=30, connect=5)
async with aiohttp.ClientSession(timeout=timeout) as session:
async with session.get("https://api.ipify.org?format=json", proxy=PROXY) as resp:
print(resp.status, await resp.json())
async with session.get("http://httpbin.org/ip", proxy=PROXY) as resp:
print(resp.status, await resp.json())
asyncio.run(main())
Credentials in the URL authenticated both requests: the HTTPS one inside the CONNECT tunnel, the plain HTTP one as a header on the forwarded request. Percent-encode a password containing @, : or / with urllib.parse.quote(password, safe="").
To make one proxy the default for a whole session, aiohttp 3.14 accepts aiohttp.ClientSession(proxy=PROXY). A proxy= on an individual call still overrides it.
proxy_auth and BasicAuth are deprecated
Older tutorials pass proxy_auth=aiohttp.BasicAuth(user, password). It still works on 3.14, but both BasicAuth and proxy_auth now emit a DeprecationWarning and are marked for removal in aiohttp 4. The warning suggests proxy_headers={"Proxy-Authorization": aiohttp.encode_basic_auth(user, password)} instead. In our run that header authenticated HTTPS targets but not a plain http:// target, which got a 407. Credentials in the URL covered both, so that is the form we recommend.
Proxy environment variables: trust_env
aiohttp ignores HTTP_PROXY and HTTPS_PROXY unless you ask:
async with aiohttp.ClientSession(trust_env=True) as session:
async with session.get("https://api.ipify.org?format=json") as resp:
print(await resp.json())
With trust_env=True it also reads NO_PROXY and credentials from ~/.netrc, as the aiohttp proxy docs describe. We checked the default too: with HTTPS_PROXY set, a plain ClientSession() went direct. That is the reverse of Python requests and HTTPX, which read the variables unless told not to.
aiohttp SOCKS5 proxy with aiohttp-socks
Passing socks5:// to proxy= does not work: aiohttp speaks HTTP to the SOCKS port and fails with Bad status line. Install the connector package:
pip install aiohttp-socks
from aiohttp_socks import ProxyConnector
async def main():
connector = ProxyConnector.from_url("socks5://USERNAME:PASSWORD@HOST:SOCKS5_PORT")
async with aiohttp.ClientSession(connector=connector) as session:
async with session.get("https://api.ipify.org?format=json") as resp:
print(await resp.json())
Two details from our run. The hostname reached the SOCKS5 proxy by default, so DNS is resolved on the proxy side; from_url(..., rdns=False) made our machine resolve it and send a bare IP instead. And the socks5h:// scheme is rejected with ValueError: Invalid scheme component, so do not copy it over from requests. Because the connector belongs to the session, rotating SOCKS5 proxies means one session per proxy.
Rotate proxies with a semaphore
With HTTP proxies, rotation is one line: pick a different proxy= per call. A semaphore caps how many requests are in flight, so you do not open a thousand tunnels at once.
import asyncio
import itertools
import os
import aiohttp
endpoints = os.environ["PROXY_URLS"].split(",")
async def fetch(session, semaphore, url, proxy):
async with semaphore:
async with session.get(url, proxy=proxy) as resp:
resp.raise_for_status()
return await resp.json()
async def main():
pool = itertools.cycle(endpoints)
semaphore = asyncio.Semaphore(4)
urls = ["https://httpbin.org/ip"] * 8
timeout = aiohttp.ClientTimeout(total=30, connect=5)
async with aiohttp.ClientSession(timeout=timeout) as session:
tasks = [fetch(session, semaphore, url, next(pool)) for url in urls]
for result in await asyncio.gather(*tasks, return_exceptions=True):
print(result)
asyncio.run(main())
The eight requests alternated between our two proxies, and the proxy logs showed five tunnels, not eight: aiohttp keeps connections alive per proxy within a session. return_exceptions=True keeps one failed proxy from cancelling the batch; check each result and retry failures on a different endpoint. Rotate proxies in Python adds health checks and cooldowns, and static vs rotating proxies covers when rotation helps at all.
Common aiohttp proxy errors
| Error | Cause | Fix |
|---|---|---|
ClientHttpProxyError: 407, message='Proxy Authentication Required' | Wrong credentials, HTTPS target | Re-copy from the order; percent-encode the password |
resp.status == 407 | Same, on an http:// target | Same fix |
ClientProxyConnectionError: Cannot connect to host | Wrong host or port, or a port for another protocol | Check the HTTP port on the order |
DeprecationWarning about proxy_auth or BasicAuth | Pre-3.14 style code | Credentials in the proxy URL |
Bad status line with a socks5:// proxy | proxy= only speaks HTTP proxies | aiohttp-socks ProxyConnector |
ValueError: Invalid scheme component: socks5h | aiohttp-socks does not accept socks5h:// | socks5://, which resolves remotely by default |
For status codes from the target site, see proxy error codes.