Integration · HTTP clients
How to use a proxy with HTTPX
In HTTPX, the proxy goes on the client: httpx.Client(proxy="http://user:pass@host:port"), or the same on AsyncClient. The older proxies= argument was deprecated in 0.26 and removed in 0.28, which is why code copied from older answers now fails with a TypeError.
Reviewed 30 Oct 2026
http://USERNAME _pool-arcfast _country-de:PASSWORD@ proxy.arcproxies.io:8000The short answer
- HTTPX 0.28 takes
proxy=(one URL) ormounts=(routing per scheme or domain);proxies=is gone. - One client per identity: the proxy is fixed when the client is created, so a new sticky session means a new client.
- Rejected credentials raise
httpx.ProxyErroron HTTPS targets; the built-in transport retries only connection errors. - SOCKS5 needs
httpx[socks], then asocks5://proxy URL.
Setup
Set up HTTPX with a proxy, step by step
- 01
Install HTTPX
Check the version first if you are reusing older code: anything that passes
proxies=needs 0.27 or earlier.shell3 linespip install httpxpip install "httpx[socks]" # optional, for SOCKS5python -c "import httpx; print(httpx.__version__)" - 02
Create a client with the proxy and check the exit
The credentials are the userinfo part of the proxy URL. The IP echo response shows the exit the target sees.
check_exit.py5 linesimport httpxwith httpx.Client(proxy="http://USERNAME_pool-arcfast_country-de:PASSWORD@proxy.arcproxies.io:8000", timeout=30) as client: r = client.get("https://api.ipify.org?format=json") print(r.json()) # {"ip": "…"} - 03
Async: one client per sticky session
AsyncClienttakes the same argument. Because the proxy is set per client, a task that needs its own IP gets its own client with its own_session-handle. A semaphore caps concurrent tasks.tasks.py22 linesimport asyncioimport secretsimport httpxlimit = asyncio.Semaphore(10)async def crawl_listing(start_url, pages=3): proxy = f"http://USERNAME_pool-arcfast_country-de_session-{secrets.token_hex(4)}_sesstime-10:PASSWORD@proxy.arcproxies.io:8000" async with limit, httpx.AsyncClient(proxy=proxy, timeout=30) as client: out = [] for page in range(1, pages + 1): r = await client.get(start_url, params={"page": page}) out.append(r.status_code) return outasync def main(): starts = ["https://example.com/c/lamps", "https://example.com/c/chairs"] print(await asyncio.gather(*(crawl_listing(u) for u in starts)))asyncio.run(main()) - 04
Route different domains through different pools with mounts
mountsmaps URL patterns to transports, each with its own proxy. Here one domain goes through ARC Basic (_pool-arc, rotating ISP) and everything else through ARC Fast in France. The most specific pattern wins.mounts.py14 linesimport httpxmounts = { "all://api.example.com": httpx.HTTPTransport( proxy="http://USERNAME_pool-arc_country-de:PASSWORD@proxy.arcproxies.io:8000", ), "all://": httpx.HTTPTransport( proxy="http://USERNAME_pool-arcfast_country-fr:PASSWORD@proxy.arcproxies.io:8000", ),}with httpx.Client(mounts=mounts, timeout=30) as client: print(client.get("https://api.example.com/v1/items").status_code) print(client.get("https://example.fr/catalogue").status_code) - 05
Handle 407, timeouts and rate limits
Catch
httpx.ProxyErrorand check for 407: that is a configuration problem, not a transient one. Retry timeouts and 429/5xx on a new handle with backoff.HTTPTransport(retries=…)only coversConnectErrorandConnectTimeout, so status-based retries are yours to write.fetch.py24 linesimport secretsimport timeimport httpxdef fetch(url, attempts=4): for attempt in range(attempts): proxy = f"http://USERNAME_pool-arcfast_country-de_session-{secrets.token_hex(4)}_sesstime-5:PASSWORD@proxy.arcproxies.io:8000" transport = httpx.HTTPTransport(proxy=proxy, retries=1) # connect errors only try: with httpx.Client(transport=transport, timeout=30) as client: r = client.get(url) except httpx.ProxyError as err: if "407" in str(err): raise RuntimeError("proxy rejected the credentials") from err except httpx.TimeoutException: pass else: if r.status_code == 407: raise RuntimeError("proxy rejected the credentials") if r.status_code != 429 and r.status_code < 500: return r time.sleep(2 ** attempt) raise RuntimeError(f"gave up on {url}")
HTTPX proxy arguments by version
Most broken HTTPX proxy code is version drift. The changelog is explicit:
| Version | proxies= | proxy= | mounts= |
|---|---|---|---|
| 0.25 and earlier | Supported | Not available | Supported |
| 0.26 (Dec 2023) | Deprecated, still works | Added | Supported |
| 0.28 (Nov 2024) and later | Removed | Supported | Supported |
A proxies={"http://": …, "https://": …} dict translates to mounts={"http://": httpx.HTTPTransport(proxy=…), "https://": httpx.HTTPTransport(proxy=…)}, or to a single proxy= when both point at the same gateway, which is the ARC case.
Rotation, keep-alive and connection limits
A client pools connections (by default up to 100, with 20 kept alive). An HTTPS request runs inside a CONNECT tunnel that stays on one exit, so a rotating username on a long-lived client can keep the same IP for many requests to one host. For a guaranteed change, use a new _session- handle and a new client; for a guaranteed hold, use a sticky handle and keep the client.
httpx.Limits(max_connections=…, max_keepalive_connections=…) caps how hard one client pushes. Throughput through more exits does not change a site's own limits; handling 429 rate limits covers pacing.
SOCKS5 in HTTPX
With httpx[socks] installed, httpx.Client(proxy="socks5://user:pass@host:port") works against the same gateway port with the same credentials. There is no advantage over http:// for HTTPS targets, which are encrypted end to end either way; use SOCKS5 when a tool in the chain only speaks SOCKS.
The problem
Common pitfalls
proxies= on HTTPX 0.28
Raises TypeError: Client.__init__() got an unexpected keyword argument 'proxies'. Switch to proxy= or mounts=.
Passing both proxy= and transport=
Put the proxy on the transport (HTTPTransport(proxy=…, retries=…)) when you need transport options; a client-level proxy= builds its own transports for matching URLs.
One client shared by tasks that need different IPs
The proxy is fixed per client. Tasks that need separate sticky sessions need separate clients.
trust_env picking up a stray proxy
HTTPX reads HTTP_PROXY and HTTPS_PROXY by default (trust_env=True). An explicit proxy= makes the intent clear.
FAQ
Questions
Something not covered? Write to contact@arcproxies.io.
How do I set a proxy in HTTPX?
Pass it when creating the client: httpx.Client(proxy="http://user:pass@host:port") or httpx.AsyncClient(proxy=…). For one-off calls, httpx.get(url, proxy=…) takes the same argument.
Why does HTTPX say unexpected keyword argument 'proxies'?
proxies= was deprecated in HTTPX 0.26 and removed in 0.28. Use proxy= for a single proxy URL, or mounts= with httpx.HTTPTransport(proxy=…) to route different schemes or domains differently.
How do I use different proxies for different sites in HTTPX?
Use mounts: a dict from URL patterns such as "all://example.com" to httpx.HTTPTransport(proxy=…). The most specific pattern wins, and "all://" catches everything else.
Does HTTPX retry failed proxy requests?
Only connection failures, and only if you set HTTPTransport(retries=n): it retries ConnectError and ConnectTimeout. Retries on 429 or 5xx responses, or on read timeouts, are up to your code.
Sources & review
Last reviewed by the ARC Proxies team. Prices are read from the live catalog at build time.
- [1]HTTPX: Proxies
- [2]HTTPX: Transports: retries and mounts
- [3]encode/httpx: CHANGELOG (0.26.0, 0.28.0)
Next
What people build with it
Integrations
Other http clients
Get started
Get credentials and paste them in.
Sign up, buy a gigabyte, generate a username in the dashboard. Code WELCOME15 takes 15% off your first purchase; bandwidth never expires.