# aiohttp proxies in 3.14: auth without BasicAuth, env vars and the errors you'll hit

Source: https://ipvolt.com/guides/aiohttp-proxy-setup
Markdown: https://ipvolt.com/guides/aiohttp-proxy-setup.md

[Home](https://ipvolt.com/index.md) / [Guides](https://ipvolt.com/guides.md) / [aiohttp proxies in 3.14: auth without BasicAuth, env vars and the errors you'll hit](https://ipvolt.com/guides/aiohttp-proxy-setup.md)

Category: Integration
Published: 2026-10-06
Reading time: 7 minutes
Author: ipvolt

Set up an HTTP or SOCKS proxy in aiohttp 3.14: replace the deprecated BasicAuth and proxy_auth, turn on trust_env, and read the 407 and Bad status line errors.

aiohttp 3.14.0 (1 June 2026) deprecated `BasicAuth` and the `proxy_auth` parameter ([changelog](https://docs.aiohttp.org/en/stable/changes.html), [#12499](https://github.com/aio-libs/aiohttp/pull/12499)). The replacement is a `Proxy-Authorization` header built with `aiohttp.encode_basic_auth()`:

```python
import asyncio

import aiohttp

PROXY = "http://proxy.example.com:8080"
PROXY_HEADERS = {"Proxy-Authorization": aiohttp.encode_basic_auth("user", "pass")}


async def main() -> None:
    async with aiohttp.ClientSession(proxy=PROXY) as session:
        async with session.get("https://example.com/", proxy_headers=PROXY_HEADERS) as response:
            print(response.status)


asyncio.run(main())
```

In our lab this authenticated every `https://` request with no `DeprecationWarning`. It did not authenticate plain `http://` requests: aiohttp 3.14.4 sent `proxy_headers` only with the `CONNECT` request that opens an HTTPS tunnel, so for an `http://` URL the proxy received no credentials and answered 407.

| Credentials passed as | `https://` URL | `http://` URL | Deprecation warnings |
| --- | --- | --- | --- |
| `proxy_headers={"Proxy-Authorization": aiohttp.encode_basic_auth(...)}` | 200 | 407 | none |
| `proxy="http://user:pass@host:port"` | 200 | 200 | none |
| `proxy_auth=aiohttp.BasicAuth(...)` | 200 | 200 | two per call |

These results were recorded on 6 October 2026 with aiohttp 3.14.4 and aiohttp-socks 0.12.0 on Python 3.14.4, run with `python -W always` against a local proxy that requires Basic authentication. aiohttp 3.14.0 and 3.14.3 gave the same results.

## Migrating from proxy_auth=aiohttp.BasicAuth

The old call still works in 3.14.4 for both URL schemes:

```python
async with session.get(url, proxy=PROXY, proxy_auth=aiohttp.BasicAuth("user", "pass")) as response:
    print(response.status)
```

Each call printed two warnings:

```text
DeprecationWarning: BasicAuth is deprecated and will be removed in aiohttp 4.0; use aiohttp.encode_basic_auth() with headers={'Authorization': ...} instead
DeprecationWarning: The 'proxy_auth' parameter is deprecated and will be removed in v4; pass proxy_headers={'Proxy-Authorization': aiohttp.encode_basic_auth(login, password)} instead
```

The replacement:

```python
proxy_headers = {"Proxy-Authorization": aiohttp.encode_basic_auth("user", "pass")}
async with session.get(url, proxy=PROXY, proxy_headers=proxy_headers) as response:
    print(response.status)
```

Check four things before you ship it:

- **Plain `http://` URLs lose their credentials.** The [documentation](https://docs.aiohttp.org/en/stable/client_advanced.html#proxy-support) shows `proxy_headers` with an `http://` URL. In the lab that request reached the proxy without `Proxy-Authorization`, and without a custom header we added to `proxy_headers`, and came back as 407. [Issue #4422](https://github.com/aio-libs/aiohttp/issues/4422) reported the same against aiohttp 3.6.2. If you fetch `http://` URLs, put the credentials in the proxy URL: `proxy="http://user:pass@host:port"` returned 200 for both schemes with no warning.
- **`ClientSession` has no `proxy_headers` argument.** The documentation shows `ClientSession(proxy=..., proxy_headers=...)`. In 3.14.4 it raised `TypeError: ClientSession.__init__() got an unexpected keyword argument 'proxy_headers'`. Set `proxy=` on the session and pass `proxy_headers=` with each request, as in the first example.
- **Don't move the header to `headers=`.** For an `https://` URL, request headers travel inside the tunnel. With `Proxy-Authorization` in `headers=`, the lab's destination server received the proxy credentials.
- **The default encoding changed.** `encode_basic_auth()` uses UTF-8 and `BasicAuth` used latin1. For the password `päss` the two produced different header values, so pass `encoding="latin1"` if your proxy expects the old bytes.

`encode_basic_auth()` is new in 3.14 ([reference](https://docs.aiohttp.org/en/stable/client_reference.html#aiohttp.encode_basic_auth)). On aiohttp 3.13.5 the same code raised `AttributeError: module aiohttp has no attribute encode_basic_auth`.

## Environment variables need trust_env=True

A default `ClientSession()` ignores proxy variables. With `HTTP_PROXY` set, the request went straight to the target and the proxy logged nothing. This session used the proxy:

```python
async with aiohttp.ClientSession(trust_env=True) as session:
    async with session.get("http://example.com/") as response:
        print(response.status)
```

The [documentation](https://docs.aiohttp.org/en/stable/client_advanced.html#proxy-support) says aiohttp reads the variables through `urllib.request.getproxies()`, applies them to the HTTP, HTTPS, WS and WSS schemes, lets hosts in `no_proxy` bypass the proxy, and can take proxy credentials from `~/.netrc`. In the lab, with `trust_env=True`:

- `HTTP_PROXY` and lowercase `http_proxy` applied to `http://` URLs, and `HTTPS_PROXY` to `https://` URLs. With only `HTTP_PROXY` set, an `https://` request went direct.
- `ALL_PROXY` was ignored.
- Credentials in the variable's URL authenticated both schemes and printed no warning.
- `NO_PROXY=127.0.0.1` sent the request direct. It had no effect on an explicit `proxy=` argument, which also took priority over the variable.
- A variable holding an `https://` proxy URL was skipped. aiohttp logged `HTTPS proxies https://127.0.0.1:8888 are not supported, ignoring` and connected directly.

[Proxy environment variables](/guides/proxy-environment-variables) compares these rules across other clients.

## ClientHttpProxyError: 407 on https:// URLs

When the proxy rejects the `CONNECT` request, aiohttp raises before anything reaches the destination. With a wrong password in the proxy URL:

```text
aiohttp.client_exceptions.ClientHttpProxyError: 407, message='Proxy Authentication Required', url='http://user:wrong@127.0.0.1:8888'
```

The password is in the message, so it goes wherever the exception goes: logs, tracebacks, error trackers. With the credentials in `proxy_headers`, or taken from `HTTPS_PROXY`, the same failure printed `url='http://127.0.0.1:8888'`.

Keep credentials out of the proxy URL for `https://` traffic. Where you need them in the URL, log `exc.status` and `exc.message` (`407` and `Proxy Authentication Required` in the lab) instead of the exception text. [Fix proxy error 407](/guides/fix-proxy-error-407) gives the order of checks when the credentials look right.

## Status 407 with no exception on http:// URLs

For an `http://` URL the proxy's 407 is an ordinary response. Nothing is raised, `response.status` is 407 and the `Proxy-Authenticate` header holds the challenge. Code that only catches exceptions treats this as success.

Check `response.status`, or create the session with `raise_for_status=True`. That raised:

```text
aiohttp.client_exceptions.ClientResponseError: 407, message='Proxy Authentication Required', url='http://127.0.0.1:8080/'
```

`raise_for_status=True` on the request and `response.raise_for_status()` raised the same error. The class is `ClientResponseError` and the URL is the target, not the proxy. `ClientHttpProxyError` is a subclass of it, so `except aiohttp.ClientResponseError` with a check for `exc.status == 407` covers both schemes.

## Bad status line: b'\x05\x00' from a socks5:// proxy URL

aiohttp does not reject `proxy="socks5://..."`. It sends an HTTP request to the SOCKS port and fails on the reply:

```text
aiohttp.client_exceptions.ClientResponseError: 400, message="Bad status line:\n  Expected HTTP/, RTSP/ or ICE/:\n\n  b'\\x05\\x00'\n    ^", url='http://127.0.0.1:8080/'
```

`\x05\x00` is the start of the SOCKS5 server's reply, read as if it were an HTTP status line. `socks5h://` failed the same way. The fix is a SOCKS connector, shown below.

## ClientConnectorSSLError from an https:// proxy URL

An `https://` proxy URL tells aiohttp to open TLS to the proxy itself. Against the lab proxy, which speaks plain HTTP, the request failed during the handshake:

```text
aiohttp.client_exceptions.ClientConnectorSSLError: Cannot connect to host 127.0.0.1:8888 ssl:default [[SSL: RECORD_LAYER_FAILURE] record layer failure (_ssl.c:1081)]
```

The proxy saw a TLS ClientHello and answered with a plaintext 400. The scheme of the proxy URL describes the connection to the proxy, not to the destination: an `http://` proxy URL carries `https://` requests through `CONNECT`. The error text comes from OpenSSL 3.5.5 and may read differently on other versions.

## DeprecationWarning: BasicAuth is deprecated

Constructing `aiohttp.BasicAuth(...)` is enough to trigger the first warning, before any request. The `auth=` parameter for website credentials is deprecated too. Its warning names `headers={'Authorization': aiohttp.encode_basic_auth(login, password)}` as the replacement.

You may not see either warning. By default Python shows a `DeprecationWarning` only when [the code that triggered it is in `__main__`](https://docs.python.org/3/library/warnings.html#default-warning-filter). In the lab the deprecated call printed its warnings when it was in the script being run and printed nothing when it was in an imported module. `python -W always` printed them in both places. `python -W error` turned the first one into an exception, which finds every call site in a test run.

## SOCKS proxies: use aiohttp-socks

```sh
python -m pip install aiohttp-socks
```

```python
import aiohttp
from aiohttp_socks import ProxyConnector

connector = ProxyConnector.from_url("socks5://127.0.0.1:1080")
async with aiohttp.ClientSession(connector=connector) as session:
    async with session.get("https://example.com/") as response:
        print(response.status)
```

With [aiohttp-socks](https://pypi.org/project/aiohttp-socks/) 0.12.0 this returned 200 for `http://` and `https://` URLs, and the SOCKS server logged each connection. Two details:

- `socks5://` already sends the hostname to the proxy. For `http://localhost:8080/` the server logged a domain name. With `rdns=False` it logged `127.0.0.1`.
- `ProxyConnector.from_url("socks5h://...")` raised `ValueError: Invalid scheme component: socks5h`. [socks5 vs socks5h](/blog/socks5-vs-socks5h) shows what each client does with the two schemes.

Requests and HTTPX need their own SOCKS packages, covered in [Missing dependencies for SOCKS support](/guides/fix-missing-dependencies-for-socks-support).

## Retries after proxy errors

A 407, the SOCKS status-line error and the TLS error above are configuration errors. The same request fails the same way again, so fix the setup instead of retrying. For timeouts and dropped connections, retry only requests that are safe to repeat. A `POST` may have reached the destination before the proxy connection failed. [Proxy retries: one lost response, two created jobs](/blog/proxy-retries-duplicate-jobs) shows that case and what an idempotency key changes.

If you use HTTPX for async work, see [HTTPX async proxies](/guides/httpx-async-proxy). For diagnostic help inside a coding agent, [Proxy Toolkit MCP](/mcp) accepts sanitized error descriptions. Keep credentials out of its arguments.

## How this was tested

On Ubuntu 26.04.1 with Python 3.14.4 and OpenSSL 3.5.5, everything on `127.0.0.1`: `python -m http.server` as the HTTP and HTTPS targets, a proxy that requires Basic authentication and logs every request with its header names, a SOCKS5 server that logs each destination, and an HTTPS target that reports the headers it received. Each of the 67 cases ran in a new interpreter with a clean environment. No proxy account was used.

The [lab archive](https://ipvolt.com/downloads/aiohttp-proxy-setup/aiohttp-proxy-lab.zip) contains the [runner](https://ipvolt.com/downloads/aiohttp-proxy-setup/run_lab.py), the [cases](https://ipvolt.com/downloads/aiohttp-proxy-setup/cases.py), the [proxy](https://ipvolt.com/downloads/aiohttp-proxy-setup/auth_proxy.py), the [SOCKS5 server](https://ipvolt.com/downloads/aiohttp-proxy-setup/socks5_server.py), the [README](https://ipvolt.com/downloads/aiohttp-proxy-setup/README.md) and the [recorded results](https://ipvolt.com/downloads/aiohttp-proxy-setup/results.json).

Not tested: macOS and Windows, Digest and NTLM proxy authentication, authenticated SOCKS proxies, WebSocket requests, `~/.netrc` credentials and proxies reached over TLS.

ipvolt is a proxy service for developers that isn't open yet; [join the early-access list](https://ipvolt.com/#waitlist-closing) for one email when it opens.

## Sources & further reading

- [aiohttp changelog: 3.14.0](https://docs.aiohttp.org/en/stable/changes.html)
- [aiohttp pull request #12499: Deprecate auth. Add encode_basic_auth](https://github.com/aio-libs/aiohttp/pull/12499)
- [aiohttp Advanced Client Usage: proxy support and trust_env](https://docs.aiohttp.org/en/stable/client_advanced.html#proxy-support)
- [aiohttp Client Reference: encode_basic_auth and proxy_headers](https://docs.aiohttp.org/en/stable/client_reference.html#aiohttp.encode_basic_auth)
- [aiohttp issue #4422: proxy_headers not passed to proxy](https://github.com/aio-libs/aiohttp/issues/4422)
- [aiohttp-socks on PyPI](https://pypi.org/project/aiohttp-socks/)
- [Python warnings: the default warning filter](https://docs.python.org/3/library/warnings.html#default-warning-filter)

## Related guides

- [Fix proxy error 407 without guessing](https://ipvolt.com/guides/fix-proxy-error-407.md)
- [Proxy environment variables: HTTP_PROXY and NO_PROXY](https://ipvolt.com/guides/proxy-environment-variables.md)
- [HTTPX async proxies: setup and PoolTimeout diagnosis](https://ipvolt.com/guides/httpx-async-proxy.md)
- [Fix "Missing dependencies for SOCKS support" in Requests, pip and HTTPX](https://ipvolt.com/guides/fix-missing-dependencies-for-socks-support.md)
- [Python Requests proxy: proxies dict, auth, SOCKS5](https://ipvolt.com/guides/python-requests-proxy.md)

## About ipvolt

Examples use generic proxy settings, with links to the original technical documentation. Product-specific behavior must be checked with your provider. ipvolt is still in development.

## Know when access opens.

ipvolt · In development

We’re building proxy infrastructure for developers and data teams. Join the interest list for a heads-up when ipvolt is ready.

Consent: One email when access opens. Nothing else.

[Get early access](https://ipvolt.com/guides/aiohttp-proxy-setup#waitlist-closing). Use the email form on this page to join the interest list.

[Privacy](https://ipvolt.com/privacy)

