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

  1. Open your order in the dashboard at https://app.proxyhive.io.
  2. 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.
  3. 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

ErrorCauseFix
ClientHttpProxyError: 407, message='Proxy Authentication Required'Wrong credentials, HTTPS targetRe-copy from the order; percent-encode the password
resp.status == 407Same, on an http:// targetSame fix
ClientProxyConnectionError: Cannot connect to hostWrong host or port, or a port for another protocolCheck the HTTP port on the order
DeprecationWarning about proxy_auth or BasicAuthPre-3.14 style codeCredentials in the proxy URL
Bad status line with a socks5:// proxyproxy= only speaks HTTP proxiesaiohttp-socks ProxyConnector
ValueError: Invalid scheme component: socks5haiohttp-socks does not accept socks5h://socks5://, which resolves remotely by default

For status codes from the target site, see proxy error codes.

Next steps

  • One client per proxy, requests-style API: HTTPX.
  • A crawl queue, retries and pipelines out of the box: Scrapy.
  • Connection reference: the docs.