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.
Sep 16, 2016
1.0.0: proxy= and proxy_auth=
on client.get() and family
Oct 18, 2017
2.3.0: trust_env=True
and proxy_headers=
Oct 31, 2021
3.8.0: no_proxy honored
and code for HTTPS proxies
Nov 13, 2024
3.11.0: ClientSession(proxy=)
a default proxy for the session
Jan 3, 2026
3.13.3: 407 fix
proxy login kept on reused connections
Jun 1, 2026
3.14.0: proxy_auth deprecated
encode_basic_auth() and proxy_headers
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.
proxy= on the request
used first
ClientSession(proxy=)
since 3.11.0, when the request has none
The environment
only with trust_env=True and no proxy above
No proxy
also for hosts listed in no_proxy
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
| Option | Since | Note |
|---|---|---|
| proxy | 1.0.0 (2016) | On each request: the proxy URL |
| Proxy headers | 2.3.0 (2017) | On each request: headers sent to the proxy |
| trust_env | 2.3.0 (2017) | On the session: reads proxy variables; no_proxy since 3.8.0 |
| Session proxy | 3.11.0 (2024) | A default proxy and login for the session |
| Login helper | 3.14.0 (2026) | Builds the login header, in UTF-8 |
| proxy_auth | Deprecated in 3.14.0 | On a request or the session; removed in 4.0 |
| SOCKS | aiohttp 3.10.0 or later | An 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_proxyhas 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://orwss://is dropped, with the log line "%s proxies %s are not supported, ignoring". Pass such a proxy withproxy=on the request. - Logins from netrc. When a proxy URL from the environment carries no login, aiohttp looks for one in
~/.netrc, or~/_netrcon Windows. Version 3.13.2 fixed loading that default file whenNETRCis 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,
rdnsdefaults to true, so the proxy resolves the site's hostname. Passrdns=Falseto resolve it on your side. With SOCKS4 the default is false, andrdns=Truesends the name to the proxy. - Which addresses parse.
from_urlaccepts the schemes socks5, socks4 and http. Anything else,socks5h://included, raisesValueError, so therdnsflag 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:
ClientProxyConnectionErrorcomes fromClientConnectorError: aiohttp could not open a connection to the proxy.ClientHttpProxyErrorcomes fromClientResponseError: 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=toClientSession, 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.
- aiohttp docs, Advanced Client Usage, Proxy support, at version 3.14.5.
- aiohttp docs, Client Reference: ClientSession, request parameters, ClientTimeout, TCPConnector, encode_basic_auth, BasicAuth, ClientRequest and the exceptions.
- aiohttp CHANGES.rst at v3.14.5.
- aiohttp source at v3.14.5:
aiohttp/client.py,aiohttp/connector.pyandaiohttp/helpers.py. - aiohttp pull request 2539, SOCKS support and pull request 9207, session proxy, through the GitHub API.
- PyPI release feeds for aiohttp and aiohttp-socks, and the aiohttp-socks metadata.
- aiohttp-socks README and its
connector.py. - python-socks README, and its
_helpers.py,socks5_async.pyandsocks4_async.py. - Python docs, urllib.request, getproxies() and no_proxy, and the Python 3.11 release notes.
- RFC 9110, HTTP Semantics: CONNECT in 9.3.6, the 407 status in 15.5.8.
- Stack Overflow questions 51887784, 35132127, 36960800, 50197767, 61278106 and 58103076 with their answers, read through the Stack Exchange API.
- Search Console figures for this page, July 15 to October 12, 2026, and worldwide volumes from Google Keyword Planner.



