An HTTPX proxy is set on the client: httpx.Client(proxy="http://USERNAME:PASSWORD@HOST:PORT"), or the same argument on httpx.AsyncClient. In httpx 0.28 the old proxies= argument is gone, so code copied from older answers fails with a TypeError. For different proxies per scheme or per host, use mounts=.
import httpx
with httpx.Client(proxy="http://USERNAME:PASSWORD@HOST:PORT") as client:
print(client.get("https://api.ipify.org?format=json").json())
Every snippet on this page ran on 2026-09-30 with httpx 0.28.1 on Python 3.12, against two local authenticating HTTP proxies and a SOCKS5 proxy that enforces username and password.
Before you start: copy your proxy details
- Open your order in the dashboard at https://app.proxyhive.io.
- Copy HOST, PORT, USERNAME and PASSWORD. Each ISP or datacenter IP is its own endpoint, with its own HTTP, HTTPS and SOCKS5 ports listed on the order. Use the HTTP port with
http://proxy URLs. - Export them:
export PROXY_HOST=HOST PROXY_PORT=PORT PROXY_USER=USERNAME PROXY_PASS=PASSWORD
A single static ISP proxy is enough to follow along; every snippet also works with a list of them.
HTTPX proxy setup: the proxy argument
Build the URL once and hand it to the client. Percent-encode the credentials so an @ or : in the password cannot split the URL in the wrong place.
import os
from urllib.parse import quote
import httpx
user = quote(os.environ["PROXY_USER"], safe="")
password = quote(os.environ["PROXY_PASS"], safe="")
proxy = f"http://{user}:{password}@{os.environ['PROXY_HOST']}:{os.environ['PROXY_PORT']}"
with httpx.Client(proxy=proxy, timeout=httpx.Timeout(30.0, connect=5.0)) as client:
r = client.get("https://api.ipify.org?format=json")
print(r.status_code, r.json())
The proxy URL uses http:// even for HTTPS sites. httpx opens a CONNECT tunnel through the proxy and runs TLS inside it, so the proxy sees the hostname and nothing else. The HTTPX proxy docs call this out as "not a typo".
The removed proxies argument
httpx.Client(proxies=proxy)
On 0.28.1 that line raises TypeError: Client.__init__() got an unexpected keyword argument 'proxies'. The fix is proxy= for one proxy, or mounts= for anything more elaborate.
Per-host and per-scheme routing with mounts
mounts maps URL patterns to transports. The most specific pattern wins, and None means "go direct".
mounts = {
"all://": httpx.HTTPTransport(proxy=os.environ["PROXY_A"]),
"all://*httpbin.org": httpx.HTTPTransport(proxy=os.environ["PROXY_B"]),
"all://localhost": None,
}
with httpx.Client(mounts=mounts) as client:
client.get("https://api.ipify.org?format=json")
client.get("https://httpbin.org/ip")
In our run the proxy logs showed the ipify request on the first proxy and the httpbin request on the second. Keys can also be "http://" and "https://" for per-scheme proxies, or "https://example.com" for one host. Use httpx.AsyncHTTPTransport inside an AsyncClient.
SOCKS5 proxy with httpx[socks]
pip install "httpx[socks]"
socks = f"socks5://{user}:{password}@{os.environ['PROXY_HOST']}:SOCKS5_PORT"
with httpx.Client(proxy=socks) as client:
print(client.get("https://api.ipify.org?format=json").json())
Without the extra, httpx refuses the URL and tells you to install it. One difference from requests: httpx sent the hostname to our SOCKS5 proxy for both socks5:// and socks5h://, so DNS resolved on the proxy side either way. If SOCKS5 is new to you, HTTP vs SOCKS5 proxies explains when it earns its place.
Environment variables and trust_env
httpx reads HTTP_PROXY, HTTPS_PROXY, ALL_PROXY and NO_PROXY when trust_env is on, which is the default. Two behaviours we confirmed:
- An explicit
proxy=beats the environment. WithHTTPS_PROXYpointing at a proxy with a wrong password,httpx.Client(proxy=proxy)still went through the right one. This is the opposite of theSessiontrap in Python requests. httpx.Client(trust_env=False)ignores the variables and goes direct.
A stray HTTPS_PROXY in a container image is still a classic cause of 407 errors in code that sets no proxy itself, so print os.environ before blaming the proxy.
Async rotation: one AsyncClient per proxy
The proxy is fixed per client, so rotation means holding one client per endpoint and choosing a client per request:
import asyncio
import itertools
import os
import httpx
async def main():
endpoints = os.environ["PROXY_URLS"].split(",")
clients = [httpx.AsyncClient(proxy=url, timeout=30) for url in endpoints]
pool = itertools.cycle(clients)
urls = ["https://httpbin.org/ip"] * 6
try:
tasks = [next(pool).get(url) for url in urls]
for r in await asyncio.gather(*tasks, return_exceptions=True):
print(r.status_code if isinstance(r, httpx.Response) else repr(r))
finally:
await asyncio.gather(*(c.aclose() for c in clients))
asyncio.run(main())
Six requests split three and three across our two proxies. Reusing clients keeps connections and TLS sessions alive per proxy; creating a client per request throws that away. For health checks, benching failing IPs and retries on a different proxy, rotate proxies in Python builds a full pool on top of this pattern.
Common httpx proxy errors
| Error | Cause | Fix |
|---|---|---|
TypeError: ... unexpected keyword argument 'proxies' | Code written for httpx 0.27 or older | proxy= or mounts= |
ProxyError: 407 Proxy Authentication Required | Wrong credentials on an HTTPS target | Re-copy from the order; quote() the password |
Response status 407 | Same, on a plain http:// target | Same fix |
ImportError mentioning httpx[socks] | SOCKS URL without the extra | pip install "httpx[socks]" |
ConnectError or ConnectTimeout | Wrong host or port, or a port for another protocol | Match the port to the scheme in your URL |
Target-side codes such as 403 and 429 are covered in proxy error codes.
Next steps
- Need per-request proxies in async code? aiohttp takes
proxy=on each call. - Stay synchronous with Python requests, or test the same endpoint from the shell with cURL.
- Connection reference: the docs.