15% off your first purchase with code WELCOME15, up to 25 GB.

See pricing

Integration · HTTP clients

How to use a proxy with aiohttp

aiohttp takes a proxy per request, session.get(url, proxy="http://user:pass@host:port"), or as a default on the ClientSession. Credentials go in the URL; the separate proxy_auth argument is deprecated since aiohttp 3.14. SOCKS5 is not built in and comes from the aiohttp-socks connector.

Reviewed 30 Oct 2026

your proxy, as a URL
http://USERNAME_pool-arcfast_country-de:PASSWORD@proxy.arcproxies.io:8000

The short answer

  • Per-request proxy= makes a sticky handle per task easy: one session, a different proxy URL per task.
  • aiohttp does not read HTTP_PROXY and friends unless you pass trust_env=True.
  • A rejected tunnel raises ClientHttpProxyError with .status == 407, and its message contains the proxy URL, password included.
  • SOCKS5 goes through aiohttp_socks.ProxyConnector, which sets one proxy per session.

Setup

Set up aiohttp with a proxy, step by step

  1. 01

    Install aiohttp

    Add aiohttp-socks only if you need SOCKS5.

    shell
    pip install aiohttppip install aiohttp-socks   # optional, for SOCKS5
  2. 02

    Request through the gateway and check the exit

    Pass the proxy URL with credentials to the request. The echo service returns the exit IP the target sees.

    check_exit.py
    import asyncioimport aiohttpPROXY = "http://USERNAME_pool-arcfast_country-de:PASSWORD@proxy.arcproxies.io:8000"async def main():    timeout = aiohttp.ClientTimeout(total=30)    async with aiohttp.ClientSession(timeout=timeout) as session:        async with session.get("https://api.ipify.org?format=json", proxy=PROXY) as r:            print(await r.json())   # {"ip": "…"}asyncio.run(main())
  3. 03

    Many tasks, one session, one sticky handle per task

    Because the proxy is a per-request argument, one ClientSession can serve tasks that each hold their own exit through a sticky session. TCPConnector(limit=…) caps open connections overall; the semaphore caps tasks.

    tasks.py
    import asyncioimport secretsimport aiohttpasync def crawl_listing(session, start_url, pages=3):    proxy = f"http://USERNAME_pool-arcfast_country-de_session-{secrets.token_hex(4)}_sesstime-10:PASSWORD@proxy.arcproxies.io:8000"    statuses = []    for page in range(1, pages + 1):        async with session.get(start_url, params={"page": page}, proxy=proxy) as r:            await r.read()            statuses.append(r.status)    return statusesasync def main(starts):    connector = aiohttp.TCPConnector(limit=50)    limit = asyncio.Semaphore(10)    async with aiohttp.ClientSession(connector=connector,                                     timeout=aiohttp.ClientTimeout(total=30)) as session:        async def guarded(url):            async with limit:                return await crawl_listing(session, url)        print(await asyncio.gather(*(guarded(u) for u in starts)))asyncio.run(main(["https://example.com/c/lamps", "https://example.com/c/chairs"]))
  4. 04

    Geo-target per request

    The username is per request too, so each call can pick its country, or city from the published list.

    geo.py
    MARKETS = {    "pl-warsaw": "USERNAME_pool-arcfast_country-pl_city-warsaw",    "it-milan":  "USERNAME_pool-arcfast_country-it_city-milan",    "de":        "USERNAME_pool-arcfast_country-de",}async def prices(session, url):    out = {}    for market, user in MARKETS.items():        async with session.get(url, proxy=f"http://{user}:PASSWORD@proxy.arcproxies.io:8000") as r:            out[market] = r.status    return out
  5. 05

    Use SOCKS5 through aiohttp-socks

    aiohttp-socks replaces the connector, so the proxy applies to the whole session; a different SOCKS identity means a different session. For SOCKS5, the connector resolves hostnames at the proxy by default (rdns=True).

    socks.py
    import aiohttpfrom aiohttp_socks import ProxyConnectorasync def main():    connector = ProxyConnector.from_url("socks5://USERNAME_pool-arcfast_country-de:PASSWORD@proxy.arcproxies.io:8000")    async with aiohttp.ClientSession(connector=connector) as session:        async with session.get("https://api.ipify.org?format=json") as r:            print(await r.json())
  6. 06

    Handle 407, timeouts and 429 without leaking the password

    ClientHttpProxyError carries the proxy's status. Its string form includes the proxy URL with the password, so log the status, not the exception.

    fetch.py
    import asyncioimport secretsimport aiohttpasync def fetch(session, url, attempts=4):    for attempt in range(attempts):        proxy = f"http://USERNAME_pool-arcfast_country-de_session-{secrets.token_hex(4)}_sesstime-5:PASSWORD@proxy.arcproxies.io:8000"        try:            async with session.get(url, proxy=proxy) as r:                if r.status == 407:                    raise RuntimeError("proxy rejected the credentials")                if r.status != 429 and r.status < 500:                    return await r.text()        except aiohttp.ClientHttpProxyError as err:            if err.status == 407:                raise RuntimeError("proxy rejected the credentials") from None            print(f"proxy error {err.status}")       # not str(err): it contains the URL        except (aiohttp.ClientConnectionError, asyncio.TimeoutError):            pass        await asyncio.sleep(2 ** attempt)    raise RuntimeError(f"gave up on {url}")

proxy=, proxy_auth= and the session default

OptionWhereStatus
proxy="http://user:pass@host:port"Per requestDocumented; the usual choice
ClientSession(proxy=…)Session defaultDocumented; a per-request proxy= still overrides it
proxy_auth=aiohttp.BasicAuth(…)Per requestDeprecated since 3.14, to be removed in 4.0
trust_env=TrueSessionReads proxy environment variables, and credentials from ~/.netrc

aiohttp proxies HTTPS targets through a CONNECT tunnel, so the TLS session runs from your code to the site and the gateway only sees the hostname.

Rotating vs sticky with aiohttp

A username without _session- lets each new connection take a new exit, but aiohttp keeps connections alive in the connector pool, and a tunnel to one host stays on one exit for its lifetime. Requests with different credentials do not share pooled connections, so a fresh handle per request is a reliable way to get a fresh IP, and a fixed sticky handle a reliable way to keep one. Rotating vs sticky sessions covers which one a job needs.

The problem

Common pitfalls

Logging the exception text

str(ClientHttpProxyError) includes the proxy URL with the password. Log err.status and the target URL instead.

Expecting environment variables to work

Unlike requests, aiohttp ignores HTTPS_PROXY unless the session has trust_env=True. Pass proxy= explicitly.

SOCKS URLs in proxy=

proxy= only speaks HTTP to the proxy; in our test a socks5:// URL there was still sent an HTTP CONNECT, not a SOCKS handshake. For SOCKS5, use aiohttp_socks.ProxyConnector.

No timeout on the session

Set ClientTimeout(total=…) on the session; a slow exit should cost one attempt, not a stuck task.

FAQ

Questions

Something not covered? Write to contact@arcproxies.io.

How do I use a proxy with authentication in aiohttp?

Put the credentials in the proxy URL: session.get(url, proxy="http://user:pass@host:port"). The proxy_auth= argument still exists but is deprecated since aiohttp 3.14.

Can I set one proxy for the whole aiohttp session?

Yes: aiohttp.ClientSession(proxy="http://user:pass@host:port") sets a default that each request can still override with its own proxy=.

Does aiohttp support SOCKS5 proxies?

Not natively. Install aiohttp-socks and create the session with connector=ProxyConnector.from_url("socks5://user:pass@host:port"). The connector applies to the whole session.

What is ClientHttpProxyError 407 in aiohttp?

The proxy refused to open the tunnel because the credentials were missing or wrong. Check the username and password, URL-encoding special characters in the password. It is not worth retrying.

Sources & review

Last reviewed by the ARC Proxies team. Prices are read from the live catalog at build time.

  1. [1]aiohttp: Advanced client usage: Proxy support
  2. [2]aiohttp: Client reference: ClientSession
  3. [3]GitHub, romis2012/aiohttp-socks: aiohttp-socks README

Integrations

Other http clients

Python requestsUse an authenticated proxy with Python requests: the proxies argument, sticky sessions per task, geo-targeting, SOCKS5 and retries that skip 407 errors.HTTPXConfigure an authenticated proxy in HTTPX 0.28: the proxy= argument that replaced proxies=, mounts per domain, async clients, SOCKS5 and error handling.AxiosUse an authenticated proxy with Axios in Node.js: the proxy option on 1.16.1+, https-proxy-agent for older versions, sticky sessions, SOCKS5 and 407s.Node.js fetchNode's built-in fetch has no proxy option. Use undici's ProxyAgent as the dispatcher, or NODE_USE_ENV_PROXY, with sticky sessions, SOCKS5 and 407 handling.curlSend curl through an authenticated proxy with -x and -U, check the exit IP, test sticky sessions and countries, use socks5h, and read 407 failures.GoRoute Go's net/http through an authenticated proxy: Transport.Proxy with url.UserPassword, a sticky session per request context, SOCKS5 and 407 checks.RubyUse an authenticated proxy in Ruby: Net::HTTP proxy arguments, Faraday proxy options, sticky sessions per task, geo-targeting and 407 handling.PHP GuzzleRoute PHP Guzzle and plain cURL through an authenticated proxy: the proxy option, sticky sessions, geo-targeting, retries that stop on a 407.Java HttpClientUse an authenticated proxy with java.net.http.HttpClient: ProxySelector, Authenticator, the Basic-over-CONNECT setting, sticky sessions and 407s.C# HttpClientUse an authenticated proxy in .NET: WebProxy and NetworkCredential on SocketsHttpHandler, a client per sticky session, SOCKS5 and 407 handling.Rust reqwestRoute Rust reqwest through an authenticated proxy: Proxy::all with basic_auth, a Client per sticky session, socks5h, timeouts and 407 errors.wgetRun GNU Wget through an authenticated proxy: https_proxy, --proxy-user, a .wgetrc for credentials, sticky sessions for mirrors and exit codes.PostmanSend Postman requests through an authenticated proxy: the custom proxy settings, Proxy auth, checking the exit IP, sessions, countries and Newman.DockerGive Docker containers and builds an authenticated proxy: HTTP_PROXY, HTTPS_PROXY and NO_PROXY, build args, Compose, and why the daemon proxy differs.

Get started

Get credentials and paste them in.

Sign up, buy a gigabyte, generate a username in the dashboard. Code WELCOME15 takes 15% off your first purchase; bandwidth never expires.