Short answers
How do I set a proxy in aiohttp?
Pass proxy= to session.get(), with the gateway URL. In the tested aiohttp 3.14.3 example, proxy authentication goes in the request’s proxy_headers, separate from headers sent to the destination.
Can an HTTP proxy carry an HTTPS request?
Yes. aiohttp asks the gateway for a CONNECT tunnel, then checks the destination’s TLS certificate through that tunnel. The gateway URL can start with http:// while the destination starts with https://.
Does ClientSession choose a new proxy IP for every request?
No. A session can reuse an existing connection. Gateway rotation is a separate setting; one session.get() call is not proof of a new connection or exit address.
Can aiohttp use a SOCKS5 proxy?
Not through proxy=, which speaks HTTP to the gateway. Pass a connector from the aiohttp-socks package to the session instead. The SOCKS5 section below has a locally tested example that handles each connector error.
Install the tested version
Use a project environment with Python 3.11 or newer. The example was checked with Python 3.12.14 and aiohttp 3.14.3. Install the named release, then confirm that the interpreter running your worker imports it. A notebook, shell and background worker can each use a different environment.
python -m pip install "aiohttp==3.14.3" certifi
python -c "import aiohttp; print(aiohttp.__version__)"Get your complete proxy username from the connection builder and put it in the runtime variable PROXY_USER. Select a currently available pool and country. Enter the proxy password at the hidden prompt; your account password and API key do not authenticate the gateway. For an unattended job, obtain the password from your existing secret manager before starting the request.
Run one HTTPS check
import asyncio
import getpass
import json
import os
import ssl
from urllib.parse import urlsplit
import aiohttp
import certifi
async def check(server, proxy_headers, tls):
timeout = aiohttp.ClientTimeout(
total=20, connect=5, sock_connect=5, sock_read=10
)
connector = aiohttp.TCPConnector(ssl=tls, limit=5, limit_per_host=5)
try:
async with aiohttp.ClientSession(
connector=connector, timeout=timeout, trust_env=False,
cookie_jar=aiohttp.DummyCookieJar(),
) as session:
async with session.get(
"https://api.portproof.org/v1/echo-ip", proxy=server, proxy_headers=proxy_headers,
allow_redirects=False,
) as response:
response.raise_for_status()
if response.status != 200:
raise ValueError("Expected HTTP 200 without a redirect")
body = bytearray()
async for chunk in response.content.iter_chunked(8192):
if len(body) + len(chunk) > 65536:
raise ValueError("Echo response exceeds 64 KiB")
body.extend(chunk)
payload = json.loads(body)
if (not isinstance(payload, dict)
or not isinstance(payload.get("ip"), str)
or not payload["ip"]):
raise ValueError("Expected an IP address in the echo response")
print("HTTP", response.status, "exit", payload["ip"])
except aiohttp.ClientHttpProxyError as error:
raise SystemExit(f"Proxy CONNECT refused: HTTP {error.status}") from None
except aiohttp.ClientResponseError as error:
raise SystemExit(f"HTTP request refused: {error.status}") from None
except (aiohttp.ClientError, TimeoutError, ValueError) as error:
raise SystemExit(f"Check failed: {type(error).__name__}") from None
finally:
# Let TLS transports finish closing before asyncio.run stops the loop.
await asyncio.sleep(0.25)
if __name__ == "__main__":
server = os.environ.get("PROXY_SERVER", "http://gw.portproof.org:7000")
address = urlsplit(server)
if (address.scheme != "http" or not address.hostname
or address.port is None or address.username is not None
or address.password is not None or address.path not in ("", "/")
or address.query or address.fragment):
raise SystemExit("Use an HTTP gateway URL without credentials or a path")
proxy_headers = {
"Proxy-Authorization": aiohttp.encode_basic_auth(
os.environ["PROXY_USER"], getpass.getpass("Proxy password: ")
)
}
tls = ssl.create_default_context(
cafile=os.environ.get("PROXY_CA_BUNDLE") or certifi.where()
)
asyncio.run(check(server, proxy_headers, tls))Save the file and run python aiohttp_check.py. Inside an existing event loop, obtain the same settings first and await check(server, proxy_headers, tls) instead of nesting asyncio.run. The password prompt is outside the request timeout. This is a diagnostic GET: it prints the status and returned address, without printing the password, headers or complete exception details.
The 64 KiB check bounds the decoded body accumulated by this script. It is not a limit on traffic billed by the gateway: protocol overhead, compressed data and network buffering are different measurements. A successful echo confirms the route used for that request. It does not establish the result, availability or speed of a later request to another website.
Keep proxy headers off the destination request
Pass proxy_headers on session.get, as shown. The installed 3.14.3 ClientSession constructor does not accept that argument. The authentication documentation describes the newer header-based interface; older snippets may use BasicAuth and proxy_auth, deprecated in 3.14. Check the version before mixing examples.
Do not move Proxy-Authorization into the ordinary headers dictionary or use destination auth= for the proxy password. The example encodes the username and password as credentials, so punctuation in the password is not interpreted as URL syntax. Treat the encoded header as a secret too: Base64 is reversible. Keep request debugging and exception logging from recording it.
trust_env=False keeps the selected route explicit. In the local check, conflicting environment proxies and NO_PROXY=* did not replace this gateway. Certificate trust uses certifi or your approved PROXY_CA_BUNDLE. Keep that bundle trustworthy and verification enabled; an authentication error is not repaired by setting ssl=False.
Limit waiting and parallel work separately
The example allows five seconds for connection acquisition and ten seconds between response reads, within a twenty-second request timeout. These are sample settings, not service speed claims. A trickling body can satisfy each read timeout; the total timeout still ends the request. aiohttp can round longer timeout deadlines, so the observed duration need not be exactly twenty seconds. See its timeout reference.
The connector allows at most five concurrent connections, but it does not cap the number of tasks your application queues. For a larger authorised job, use a bounded work queue and a worker deadline. Reuse one appropriately scoped session, then close it. The example discards cookies; a workflow that needs a login needs deliberate cookie handling and isolation between accounts.
What we checked locally
On 30 September 2026 we executed this page’s Python snippet through a local authenticated CONNECT proxy and HTTPS origin. Credentials and certificates were synthetic. The checks covered successful authentication with password punctuation, rejected credentials, certificate validation, response-size limits, malformed JSON, redirects, a stalled response and a trickling response.
For the successful tunnel, the destination received no proxy authentication header. Redirects were not followed. Session, connector and response objects were closed; no tunnel remained after the standalone process exited. The short final pause allows TLS shutdown to progress, but a stalled peer can delay transport cleanup in a long-running process. These checks do not benchmark the live pool or verify exit-country targeting or HTTPS connections to the proxy itself. SOCKS5 has its own example and checks below.
A second local run on 1 October 2026 added a refused gateway port, an unresolvable gateway hostname, a connection that never completed and a CONNECT request that was never answered. The messages the script printed are listed under troubleshooting.
Find the failed stage before retrying
- Proxy CONNECT refused: HTTP 407
- The gateway refused the tunnel. Check the complete generated username, proxy password and remaining traffic. Follow the 407 guide.
- ClientConnectorCertificateError
- Check the destination hostname, clock and trusted certificate chain. Keep TLS verification enabled.
- Check failed: ClientProxyConnectionError or ClientConnectorDNSError
- The gateway was not reached: its port refused the connection, or its hostname did not resolve. Compare the host and port with the connection builder, then follow the connection-refused guide.
- Check failed: ConnectionTimeoutError
- The gateway connection or its answer to CONNECT did not arrive within the five-second connect limit. Check the host, the HTTP port 7000 and the network path from the worker before raising the limit.
- SocketTimeoutError or TimeoutError
- Repeat the small echo check before testing the application destination. Check worker connectivity and the configured timeouts.
- HTTP request refused: 403 or 429
- Identify which service returned the response, then follow its access rules or rate limit. Changing authentication blindly is unlikely to help.
There is no application retry loop here. The library can retry certain connection failures for eligible requests, so do not use this example to assume exactly one network attempt. Count attempts and received bytes when estimating a workload, and do not automatically replay operations with side effects. Rotation and sticky sessions explains why connection reuse matters.
Using a different client? Follow the separate HTTPX guide or Requests guide. Once your actual workflow returns useful results, use the traffic planner for a price-monitoring workload or the bandwidth guide for other jobs.
SOCKS5 with the aiohttp_socks connector
The proxy= argument speaks HTTP to the gateway, not SOCKS5. Given a socks5:// URL, aiohttp 3.14.3 sent an HTTP CONNECT to our local SOCKS5 port and failed with a connection reset instead of a clear error. For the SOCKS5 port 7001, install the third-party aiohttp-socks connector and pass it to the session. The SOCKS5 proxies page covers client authentication, DNS settings and current prices for that port.
python -m pip install "aiohttp==3.14.3" "aiohttp-socks==0.12.0" certifi
python -c "import aiohttp_socks; print(aiohttp_socks.__version__)"The same complete username from the connection builder works on both ports, so keep it in PROXY_USER and enter the proxy password at the hidden prompt. In our check, pip installed python-socks 3.1.1 as the connector’s dependency.
import asyncio
import getpass
import json
import os
import ssl
import aiohttp
import certifi
from aiohttp_socks import (
ProxyConnectionError, ProxyConnector, ProxyError, ProxyTimeoutError,
ProxyType,
)
async def check(host, port, username, password, tls):
connector = ProxyConnector(
proxy_type=ProxyType.SOCKS5, host=host, port=port,
username=username, password=password,
ssl=tls, limit=5, limit_per_host=5,
)
timeout = aiohttp.ClientTimeout(
total=20, connect=5, sock_connect=5, sock_read=10
)
try:
async with aiohttp.ClientSession(
connector=connector, timeout=timeout, trust_env=False,
cookie_jar=aiohttp.DummyCookieJar(),
) as session:
async with session.get(
"https://api.portproof.org/v1/echo-ip", allow_redirects=False,
) as response:
response.raise_for_status()
if response.status != 200:
raise ValueError("Expected HTTP 200 without a redirect")
body = bytearray()
async for chunk in response.content.iter_chunked(8192):
if len(body) + len(chunk) > 65536:
raise ValueError("Echo response exceeds 64 KiB")
body.extend(chunk)
payload = json.loads(body)
if (not isinstance(payload, dict)
or not isinstance(payload.get("ip"), str)
or not payload["ip"]):
raise ValueError("Expected an IP address in the echo response")
print("HTTP", response.status, "exit", payload["ip"])
except ProxyConnectionError:
raise SystemExit("SOCKS5 gateway unreachable") from None
except asyncio.IncompleteReadError:
raise SystemExit("SOCKS5 handshake cut off by the gateway") from None
except ProxyError as error:
raise SystemExit(f"SOCKS5 gateway refused: {error}") from None
except (ProxyTimeoutError, TimeoutError) as error:
raise SystemExit(f"No answer in time: {type(error).__name__}") from None
except aiohttp.ClientResponseError as error:
raise SystemExit(f"HTTP request refused: {error.status}") from None
except aiohttp.ClientOSError as error:
cause = error.__cause__ or error
raise SystemExit(f"Connection failed: {type(cause).__name__}") from None
except (aiohttp.ClientError, ValueError) as error:
raise SystemExit(f"Check failed: {type(error).__name__}") from None
finally:
# Let TLS transports finish closing before asyncio.run stops the loop.
await asyncio.sleep(0.25)
if __name__ == "__main__":
tls = ssl.create_default_context(
cafile=os.environ.get("PROXY_CA_BUNDLE") or certifi.where()
)
asyncio.run(check(
os.environ.get("PROXY_HOST", "gw.portproof.org"),
int(os.environ.get("PROXY_SOCKS_PORT", "7001")),
os.environ["PROXY_USER"],
getpass.getpass("Proxy password: "),
tls,
))The connector belongs to the session: every request made through it uses the SOCKS5 gateway. Requests that should not use the proxy need a separate session. The username and password reach the connector as separate arguments, so punctuation in the password needs no URL encoding. ProxyConnector.from_url() also exists; it rejected a socks5h:// URL in our check, so write socks5:// there.
By default the connector lets the proxy resolve the destination hostname, like curl’s socks5h. In our check, a hostname that only the proxy could resolve worked with the default and failed with rdns=False. The TLS context passed as ssl= verifies the destination certificate exactly as in the HTTP example.
Keep the handlers in the order shown. The connector’s three exceptions do not inherit from aiohttp’s, ProxyTimeoutError is not a TimeoutError, and a gateway that closes the connection mid-handshake raises asyncio’s IncompleteReadError. A handler for only aiohttp.ClientError and TimeoutError lets all four through as a traceback. An untrusted destination certificate arrives as ClientOSError, so the example prints the underlying cause.
- SOCKS5 gateway unreachable
- A refused port, a gateway hostname that did not resolve, or a connection the operating system gave up on. Check
PROXY_HOST, the SOCKS5 port 7001 and outbound network access. The connection-refused guide separates these cases. - SOCKS5 handshake cut off by the gateway
- The connection opened, then closed before the SOCKS5 exchange finished. Our local HTTP proxy port did this when it stopped waiting for an HTTP request. Check that
PROXY_SOCKS_PORTis the SOCKS5 port 7001, not the HTTP port 7000. - SOCKS5 gateway refused: Username and password authentication failure
- The gateway rejected the credentials during the SOCKS5 handshake. Check the complete generated username and the proxy password.
- SOCKS5 gateway refused: Host unreachable
- The gateway answered with a SOCKS5 error instead of connecting to the destination. Our local proxy sent this for a destination name it could not resolve; check the destination URL.
- No answer in time: ConnectionTimeoutError or ProxyTimeoutError
- The connection or the SOCKS5 greeting did not finish within the budget. With the settings shown, aiohttp’s
connectlimit ran out first and the name wasConnectionTimeoutError.ProxyTimeoutErrorappeared only when we setsock_connectbelowconnect(3 seconds against 5). An HTTP port that waits for an HTTP request also ends here, so check the port before raising either limit. - Connection failed: SSLCertVerificationError
- The destination certificate was not trusted. Check the hostname, clock and certificate bundle, and keep verification enabled.
On 1 October 2026 we ran this snippet with Python 3.12.14, aiohttp 3.14.3, aiohttp-socks 0.12.0 and python-socks 3.1.1 against a local SOCKS5 proxy with username and password authentication and an HTTPS origin. Credentials and certificates were synthetic. It printed the exit address with a password containing URL punctuation and for a hostname only the proxy could resolve. Each failure in the list above printed its message without a traceback: the destination error with only the URL line changed, and ProxyTimeoutError with sock_connect lowered to 3 seconds; every other row came from the snippet as printed. The checks did not use the live gateway, benchmark a pool or verify exit-country targeting.
What is not allowed
Use proxies for authorised testing, research and monitoring in accordance with the destination’s terms and the acceptable-use policy.
Read next
HTTPX proxy setup for sync and async
Tested Python Client and AsyncClient examples with proxy authentication, explicit settings and response cleanup.
Read the guide
Fix 407 Proxy Authentication Required
Read the gateway code, check credentials and balance, then separate a rejected HTTPS tunnel from an HTTP response.
Read the guide