Skip to content

How to Use Proxies With aiohttp: HTTPS, SOCKS5 and trust_env

aiohttp proxy setup from its docs and code: proxy= per request or session (3.11+), proxy logins after 3.14, trust_env, HTTPS proxies, SOCKS5.

HProxy TeamAugust 23, 2026Updated October 13, 202611 min read

How to Use Proxies With aiohttp (async Python)

aiohttp sends a request through a proxy when you pass proxy= on the request, as in session.get(url, proxy="http://proxy.com"). Since aiohttp 3.11.0, released on November 13, 2024, ClientSession takes a default proxy too, and a proxy on the request takes its place. aiohttp reads HTTP_PROXY and the other proxy variables only when the session has trust_env=True. SOCKS needs a separate package, aiohttp-socks.

Two version changes matter most. From 3.14.0, released on June 1, 2026, proxy_auth and BasicAuth are deprecated, and the login goes into a Proxy-Authorization header built with encode_basic_auth(). And by aiohttp's own code, an https:// proxy in front of an https:// site needs Python 3.11 or newer. Everything below comes from aiohttp's docs, changelog and source at version 3.14.5, the latest on PyPI, read on October 13, 2026. We ran none of the code and sent nothing through a proxy.

aiohttp's proxy features, release by release
  1. Sep 16, 2016

    1.0.0: proxy= and proxy_auth=

    on client.get() and family

  2. Oct 18, 2017

    2.3.0: trust_env=True

    and proxy_headers=

  3. Oct 31, 2021

    3.8.0: no_proxy honored

    and code for HTTPS proxies

  4. Nov 13, 2024

    3.11.0: ClientSession(proxy=)

    a default proxy for the session

  5. Jan 3, 2026

    3.13.3: 407 fix

    proxy login kept on reused connections

  6. Jun 1, 2026

    3.14.0: proxy_auth deprecated

    encode_basic_auth() and proxy_headers

Source: aiohttp CHANGES.rst and docs at v3.14.5, read October 13, 2026 · hproxy.comProxy.
Printed on our own machine on October 13, 2026, from aiohttp's changelog, docs and source, PyPI, python-socks and Stack Overflow, saved that day

How do you set a proxy in aiohttp?

The docs' first example puts the proxy on the request:

async with aiohttp.ClientSession() as session:
    async with session.get("http://python.org",
                           proxy="http://proxy.com") as resp:
        print(resp.status)

Since 3.11.0, ClientSession(proxy=...) sets a default proxy for every request of the session, and proxy_auth= a default login, deprecated since 3.14.0 like the request's. The docs' example for it also passes proxy_headers= to ClientSession. The constructor at 3.14.5 lists proxy and proxy_auth, and no proxy_headers, so that example does not match the code we read.

The pull request that added the session proxy says a proxy set on get or post "will take effect" over the session's default. aiohttp's client code shows the whole order. A proxy on the request comes first, then the session's default. The environment counts only when both are empty and trust_env is on. One result of that code: proxy=None on a request falls back to the session's proxy, so a request cannot opt out of it.

Which proxy an aiohttp request uses
  1. proxy= on the request

    used first

  2. ClientSession(proxy=)

    since 3.11.0, when the request has none

  3. The environment

    only with trust_env=True and no proxy above

  4. No proxy

    also for hosts listed in no_proxy

Source: aiohttp client.py and helpers.py at v3.14.5, read October 13, 2026 · hproxy.comProxy.

Old answers still show the session without a proxy. In a 2018 Stack Overflow question with 54,855 views, a user passed a requests-style dictionary to ClientSession(proxy=...). aiohttp had no such parameter until 3.11.0. The accepted answer moved the proxy onto session.get, which has worked since 1.0.0.

aiohttp's proxy options and when they arrived

OptionSinceNote
proxy1.0.0 (2016)On each request: the proxy URL
Proxy headers2.3.0 (2017)On each request: headers sent to the proxy
trust_env2.3.0 (2017)On the session: reads proxy variables; no_proxy since 3.8.0
Session proxy3.11.0 (2024)A default proxy and login for the session
Login helper3.14.0 (2026)Builds the login header, in UTF-8
proxy_authDeprecated in 3.14.0On a request or the session; removed in 4.0
SOCKSaiohttp 3.10.0 or laterAn add-on: SOCKS4, SOCKS5 and chains

aiohttp's changelog and docs at v3.14.5, and the README and PyPI page of the SOCKS add-on; read October 13, 2026

Proxy logins, and the change in 3.14

aiohttp takes a proxy login two ways in 3.14.5. The docs show it inside the proxy URL, as proxy="http://user:pass@some.proxy.com". Or you build the header yourself:

from aiohttp import encode_basic_auth

async with aiohttp.ClientSession() as session:
    proxy_headers = {"Proxy-Authorization": encode_basic_auth("user", "pass")}
    async with session.get("http://python.org",
                           proxy="http://proxy.com",
                           proxy_headers=proxy_headers) as resp:
        print(resp.status)

The older proxy_auth=aiohttp.BasicAuth('user', 'pass') still runs, with a warning. aiohttp 3.14.0 deprecated BasicAuth and the auth and proxy_auth parameters, which "will be removed in aiohttp 4.0". The code at 3.14.5 issues a DeprecationWarning that begins "The 'proxy_auth' parameter is deprecated and will be removed in v4" and points to encode_basic_auth(login, password) in proxy_headers. The changelog adds one difference: encode_basic_auth() defaults to UTF-8, where BasicAuth used latin1. A password with letters outside ASCII can therefore encode differently. On versions before 3.14.0 there is no encode_basic_auth(). There, the URL form or proxy_auth=aiohttp.BasicAuth(...) does the job, as the accepted 2018 answer shows. For a session-wide login, put it in the session's proxy URL: the client code hands the session's proxy to each request the same way as a proxy passed on the request.

A wrong or missing login comes back as 407. RFC 9110 says the code means the client must "authenticate itself in order to use a proxy for this request". The proxy also sends a Proxy-Authenticate challenge that names its scheme. On an HTTPS address, aiohttp raises ClientHttpProxyError when the proxy answers its CONNECT with anything other than 200. One older bug sits here too. Before 3.13.3, aiohttp could drop the proxy login when it reused a connection, "which caused 407 (Proxy authentication required) errors". What a 407 looks like in curl, Python and other tools, and the fix for each cause, is on our page about the 407 error.

HTTPS sites through a proxy, and https:// proxies

The docs open the subject with one sentence: "aiohttp supports plain HTTP proxies and HTTP proxies that can be upgraded to HTTPS via the HTTP CONNECT method". For an https:// address, aiohttp sends the proxy a CONNECT, as its code comment shows with CONNECT www.python.org:443 HTTP/1.1. Then it starts TLS with the site through that tunnel. RFC 9110 defines CONNECT as asking the recipient to "establish a tunnel" to the destination. So an ordinary http:// proxy URL is right for https:// sites.

A proxy you reach over TLS, written https://, means TLS inside TLS. Here the docs and the code disagree on the date. The docs' note still reads "As of now (Python 3.10), support for TLS in TLS is disabled". The code at 3.14.5 tells the user to "upgrade to Python 3.11 or higher". Its comment adds that support "was added in Python 3.11 (bpo-44011)", and that uvloop already supports it. Version 3.14.0 also fixed a misleading warning that fired for plain HTTP proxies.

One more note from the docs matters behind a company proxy. A custom ssl.SSLContext you pass is used for the site and for the TLS tunnel to an HTTPS proxy. Its trust chain has to recognize the certificates of both. A 2024 Stack Overflow answer from inside WSL behind a corporate proxy needed exactly that: a context built from the system's CA file.

Older versions behave differently. A 2019 question quotes aiohttp raising "Only http proxies are supported" for an https:// proxy. The docs say aiohttp 3.8 and higher has the code for TLS in TLS.

trust_env and the proxy variables

aiohttp reads no proxy settings from the environment by default, the opposite of requests. Pass trust_env=True to the session, as the docs show:

async with aiohttp.ClientSession(trust_env=True) as session:
    async with session.get("http://python.org") as resp:
        print(resp.status)

It then calls Python's urllib.request.getproxies() for the http, https, ws and wss schemes. Python's docs explain where that looks. It scans the environment for <scheme>_proxy variables first, in any letter case. When it finds none, it reads "System Configuration for macOS and Windows Systems Registry for Windows". So on Windows, trust_env=True can pick up the proxy set in Windows itself. When lower and upper case disagree, the lower case wins. When REQUEST_METHOD is set, as in a CGI script, getproxies() ignores HTTP_PROXY, because a client can inject that variable. By the docs, WS_PROXY and WSS_PROXY count too, since aiohttp 3.8.

Three details come from aiohttp's own code and changelog:

  • Bypass. no_proxy has been honored since 3.8.0. Python's docs describe it as a comma-separated "list of hostname suffixes", each with an optional port.
  • https:// values are skipped. A proxy address from the environment that starts with https:// or wss:// is dropped, with the log line "%s proxies %s are not supported, ignoring". Pass such a proxy with proxy= on the request.
  • Logins from netrc. When a proxy URL from the environment carries no login, aiohttp looks for one in ~/.netrc, or ~/_netrc on Windows. Version 3.13.2 fixed loading that default file when NETRC is not set.

SOCKS proxies with aiohttp-socks

aiohttp itself has no SOCKS support. People have asked since at least 2016: a question that year on SOCKS with aiohttp has 12,056 views. A pull request adding SOCKS to aiohttp was opened in November 2017 and closed on April 7, 2025, without being merged. In 2021 a contributor pointed to aiohttp-socks as a "quite stable 3rd party SOCKS implementation". Earlier answers on Stack Overflow name aiosocks and aiosocksy, which answers from 2018 and 2023 describe as abandoned and deprecated.

aiohttp-socks 0.12.0, released on August 12, 2026, needs aiohttp 3.10.0 or later and Python 3.9 or later. Its README says it "Supports SOCKS4(a), SOCKS5(h), HTTP (CONNECT) as well as Proxy chains". Install it with pip install aiohttp_socks. The README's example:

import aiohttp
from aiohttp_socks import ProxyType, ProxyConnector, ChainProxyConnector


async def fetch(url):
    connector = ProxyConnector.from_url('socks5://user:password@127.0.0.1:1080')
    
    ### or use ProxyConnector constructor
    # connector = ProxyConnector(
    #     proxy_type=ProxyType.SOCKS5,
    #     host='127.0.0.1',
    #     port=1080,
    #     username='user',
    #     password='password',
    #     rdns=True # default is True for socks5
    # )
    
    ### proxy chaining (since ver 0.3.3)
    # connector = ChainProxyConnector.from_urls([
    #     'socks5://user:password@127.0.0.1:1080',
    #     'socks4://127.0.0.1:1081',
    #     'http://user:password@127.0.0.1:3128',
    # ])
    async with aiohttp.ClientSession(connector=connector) as session:
        async with session.get(url) as response:
            return await response.text()

Three details sit in the code of python-socks, which aiohttp-socks uses underneath:

  • Who looks up the name. With SOCKS5, rdns defaults to true, so the proxy resolves the site's hostname. Pass rdns=False to resolve it on your side. With SOCKS4 the default is false, and rdns=True sends the name to the proxy.
  • Which addresses parse. from_url accepts the schemes socks5, socks4 and http. Anything else, socks5h:// included, raises ValueError, so the rdns flag sets where names are resolved.
  • Where the proxy lives. The proxy belongs to the connector, and the session uses it for every request. A pool of SOCKS proxies needs one connector, and so one session, for each proxy.

For the difference between the two protocols, including where the DNS lookup happens, see our page on SOCKS4 vs SOCKS5. To try aiohttp-socks against real servers, our free proxy list API hands out public SOCKS4 and SOCKS5 proxies with no key. Strangers run those servers, so send no API key, cookie or login through them.

Many requests, many proxies: limits and timeouts

Rotating HTTP proxies in aiohttp means a different proxy= on each request. aiohttp keeps such connections apart: since 3.2.1 it does not "reuse a connection with the same URL but different proxy/TLS settings". Its TCPConnector holds at most 100 connections at once by default, set with limit. limit_per_host caps one endpoint and is unlimited by default. If a pool keeps answering 407 on reused connections, check whether you run a version before 3.13.3.

Timeouts come from ClientTimeout. The docs give the session default as "300 seconds (5min) total timeout, 30 seconds socket connect timeout". The 30 seconds for connecting date from 3.10.9. The four fields are total, connect, sock_connect and sock_read. When connect or sock_connect runs out, aiohttp raises ConnectionTimeoutError. When sock_read runs out, it raises SocketTimeoutError.

The two proxy errors sit in different branches of aiohttp's exception tree:

  • ClientProxyConnectionError comes from ClientConnectorError: aiohttp could not open a connection to the proxy.
  • ClientHttpProxyError comes from ClientResponseError: the proxy answered the CONNECT with a status other than 200.
  • A proxy hostname that does not resolve raises ClientConnectorDNSError, as a comment in the connector notes.

Our checklist on avoiding IP bans while scraping covers pacing, robots.txt and backing off. Our page on proxies for web scraping compares the proxy types.

Check that the proxy is in use

Since 3.12, aiohttp passes each request to client middleware as a ClientRequest. Its proxy attribute holds "The proxy URL if the request will be sent through a proxy", and None otherwise. The free app on our proxy checker page tests the proxy itself from your own connection, and it reports the fraud score of the exit address. For speed, country and anonymity checks by hand, see how to check if a proxy is working. For synchronous code, our requests guide and httpx guide cover the same ground.

Where HProxy fits

aiohttp takes an HTTP proxy URL on each request, and aiohttp-socks takes SOCKS5. Our residential proxies start at $0.44 per GB. Premium, from $1.00 per GB, adds city and ISP targeting and keeps one address for up to 24 hours. Datacenter proxies, from $1.58 per IP, carry HTTP, HTTPS and SOCKS5, so either route works with them. For a first test, the free proxy list API returns every working public proxy we have.

What we could not find out

  • What the docs' session example does when run. It passes proxy_headers= to ClientSession, which the 3.14.5 signature does not list. We ran nothing, so we cannot say what Python prints.
  • When CPython enabled TLS in TLS. aiohttp's code says Python 3.11 and names bpo-44011. Python 3.11's release notes, which we read, do not mention it.
  • The Windows event loop case. A 2020 answer blamed Windows' ProactorEventLoop for HTTPS through an HTTP proxy. We did not check whether that still applies.
  • A page we could not read. One rival comparison page answered our reader with a security checkpoint.

Sources

All read on October 13, 2026.

Frequently asked questions

How do I set a proxy in aiohttp?

Pass it on the request: session.get(url, proxy='http://host:port'). Since aiohttp 3.11.0, released in November 2024, ClientSession also takes a default proxy, and a proxy on the request takes its place. aiohttp reads proxy environment variables only when the session has trust_env=True. An http:// proxy carries https:// requests too: aiohttp asks the proxy for a CONNECT tunnel and starts TLS through it.

Does aiohttp support SOCKS5 proxies?

Not by itself. A pull request that added SOCKS to aiohttp was opened in 2017 and closed in April 2025 without being merged. The aiohttp-socks package adds it through a connector: ProxyConnector.from_url('socks5://user:password@127.0.0.1:1080'), passed to ClientSession(connector=connector). With SOCKS5 the proxy looks up hostnames unless you pass rdns=False. A socks5h:// address is refused; the rdns flag sets where names are resolved.

Why is my aiohttp proxy being ignored?

Check four things. Before 3.11.0, ClientSession took no proxy argument, so the proxy has to go on each request. Environment variables count only with trust_env=True, and hosts in no_proxy skip them. With trust_env, aiohttp skips any proxy address that starts with https:// and logs a warning. And proxy=None on a request still means the session's default proxy, by aiohttp's own code.

How do I authenticate an aiohttp proxy?

Put the login in the proxy URL, http://user:pass@host:port, as the docs still show. From aiohttp 3.14.0 the documented way is a header: proxy_headers={'Proxy-Authorization': encode_basic_auth('user', 'pass')}. proxy_auth and BasicAuth are deprecated since 3.14.0 and will be removed in 4.0. On older versions, proxy_auth=aiohttp.BasicAuth('user', 'pass') still works. A 407 means the proxy did not accept the login.

How do I rotate proxies across concurrent aiohttp requests?

Pass a different proxy= on each request; it takes the place of the session's default proxy. aiohttp does not reuse a connection opened with other proxy settings, a fix that dates from 3.2.1. Its connector holds at most 100 connections at once unless you change limit. Versions before 3.13.3 could lose the proxy login on a reused connection and get a 407. With aiohttp-socks the proxy belongs to the connector, so each SOCKS proxy needs its own session.

Get proxies that are alive right now

Our free proxy list re-checks every exit every few minutes across 100+ countries, with a live last-checked time, so you copy IPs that worked moments ago, not a stale text dump. When the location has to survive a real check, the paid network holds up.

129M+ proxy checks run · 100+ countries · HTTP / HTTPS / SOCKS · re-checked every few minutes · no signup

HProxy.

Honest guides and comparisons on proxies, scraping and staying unblocked, from the team that runs the network.

RSS feed