HTTPX Proxy Setup in Python
HTTPX is the modern Python HTTP client with first-class sync and async APIs. This guide covers the current proxy parameter, per-route mounts, SOCKS5 via the httpx[socks] extra, authentication, and how to pair an AsyncClient with a dedicated mobile IP.
Quick Answer
Pass a proxy URL to the client with the singular proxy argument: httpx.Client(proxy="http://user:pass@host:port"). The same argument works on httpx.AsyncClient. The old plural proxies argument was deprecated in HTTPX 0.26 and removed in 0.28, so code copied from older tutorials raises a TypeError on current versions.
- →
proxy=takes one URL for all traffic; usemounts=for per-scheme or per-host routing - →SOCKS5 needs
pip install httpx[socks], which pulls in the socksio package - →Credentials go in the URL userinfo:
scheme://username:password@host:port
The examples below use the placeholder credentials from a mobileproxies.org dashboard: host proxy.mobileproxies.org, HTTP port 8000, SOCKS5 port 1080, user u_4a9c, password p_2X7q. Substitute your own values. Everything shown is documented HTTPX configuration; the patterns apply to any HTTP or SOCKS5 proxy endpoint, including the dedicated 4G/5G proxies used for web scraping.
The proxy parameter (sync client)
The supported way to route an HTTPX client through a proxy is the proxy argument at client initialization. It accepts a single proxy URL that handles all requests made by that client:
import httpx
PROXY = "http://u_4a9c:p_2X7q@proxy.mobileproxies.org:8000"
with httpx.Client(proxy=PROXY, timeout=20.0) as client:
resp = client.get("https://api.ipify.org?format=json")
resp.raise_for_status()
print(resp.json()) # egress IP of the mobile proxy, not your machineThe top-level shortcut functions accept it too, for example httpx.get(url, proxy=PROXY), though a Client is preferred for more than one request because it reuses connections.
proxies= no longer exists
Per the official changelog, HTTPX 0.26.0 (December 2023) added proxy and deprecated the dict-style proxies argument, and 0.28.0 (November 2024) removed proxies entirely. If you see TypeError: ... unexpected keyword argument 'proxies', replace it with proxy= (one URL) or mounts= (per-route routing, below).
Proxy authentication
HTTPX reads proxy credentials from the userinfo section of the proxy URL, as shown in the official docs: http://username:password@localhost:8030. If your password contains characters like @, : or /, URL-encode the parts first:
import os
from urllib.parse import quote
import httpx
user = quote(os.environ["MP_USER"]) # e.g. u_4a9c
password = quote(os.environ["MP_PASS"]) # e.g. p_2X7q
host = "proxy.mobileproxies.org"
PROXY = f"http://{user}:{password}@{host}:8000"
with httpx.Client(proxy=PROXY) as client:
print(client.get("https://api.ipify.org").text)Keep credentials in environment variables or a secrets manager rather than hard-coding them; the proxy URL ends up in tracebacks and logs surprisingly easily.
Async: AsyncClient with a proxy
httpx.AsyncClient takes exactly the same proxy, mounts and trust_env parameters as the sync client, per the HTTPX API reference. That makes concurrent fetching through one authenticated proxy a small amount of code:
import asyncio
import httpx
PROXY = "http://u_4a9c:p_2X7q@proxy.mobileproxies.org:8000"
URLS = [
"https://example.com/page/1",
"https://example.com/page/2",
"https://example.com/page/3",
]
async def fetch(client: httpx.AsyncClient, url: str) -> int:
resp = await client.get(url)
return resp.status_code
async def main() -> None:
limits = httpx.Limits(max_connections=5)
async with httpx.AsyncClient(proxy=PROXY, limits=limits, timeout=20.0) as client:
results = await asyncio.gather(*(fetch(client, u) for u in URLS))
print(results)
asyncio.run(main())One practical note when the upstream is a dedicated mobile proxy: every concurrent request exits from the same carrier IP, because a dedicated slot is one SIM and one device. That is the point - the IP is consistently yours and carries carrier-grade reputation - but it also means concurrency should be tuned to look like one busy client, not a thousand. Capping httpx.Limits(max_connections=...) to a small number is usually the right call. Request patterns matter as much as the IP itself, as we cover in how websites detect proxies.
SOCKS5 proxies in HTTPX
SOCKS support is not built into the base install. The official docs require the optional extra, which installs the socksio package:
pip install "httpx[socks]"
Then use a socks5:// proxy URL, with credentials in the same userinfo position. Since HTTPX 0.28.0 the changelog also lists socks5h as a valid proxy scheme, which resolves DNS on the proxy instead of locally:
import httpx
# socks5:// -> DNS resolved locally
# socks5h:// -> DNS resolved by the proxy (HTTPX 0.28+)
PROXY = "socks5://u_4a9c:p_2X7q@proxy.mobileproxies.org:1080"
with httpx.Client(proxy=PROXY) as client:
print(client.get("https://api.ipify.org").text)
# Works identically on the async client:
# async with httpx.AsyncClient(proxy=PROXY) as client: ...Whether SOCKS5 or HTTP CONNECT is the better fit depends on the workload; the differences are smaller than most posts suggest, and we break them down in SOCKS5 vs HTTP proxy. If you are on the requests library instead of HTTPX, the equivalent setup lives in our Python requests SOCKS5 proxy example.
Per-route proxies with mounts
When one URL is not enough - different proxies per scheme, a proxy only for certain hosts, or some traffic going direct - HTTPX uses the mounts parameter. You map URL patterns to transport instances, each carrying its own proxy:
import httpx
PROXY = "http://u_4a9c:p_2X7q@proxy.mobileproxies.org:8000"
mounts = {
# Note: the value for the "https://" key still uses an
# http:// proxy URL. The docs call this out explicitly -
# it is the scheme of the proxy connection, not the target.
"http://": httpx.HTTPTransport(proxy=PROXY),
"https://": httpx.HTTPTransport(proxy=PROXY),
}
with httpx.Client(mounts=mounts) as client:
...Patterns can also target hosts. A mount key of "all://" matches everything, and wildcard forms like "all://*example.org" match a domain and its subdomains, so you can proxy only the sites that need a mobile IP and let internal traffic go direct (URLs that match no mount fall back to the client default transport, which connects directly when no proxy= is set):
import httpx
MOBILE = "http://u_4a9c:p_2X7q@proxy.mobileproxies.org:8000"
mounts = {
# Only target-site traffic uses the mobile proxy slot us-mob-01
"all://*target-site.com": httpx.HTTPTransport(proxy=MOBILE),
}
# Async variant: same mapping, async transport class
async_mounts = {
"all://*target-site.com": httpx.AsyncHTTPTransport(proxy=MOBILE),
}
client = httpx.Client(mounts=mounts)
# async_client = httpx.AsyncClient(mounts=async_mounts)For AsyncClient, mount httpx.AsyncHTTPTransport instances; the class accepts the same proxy argument as its sync counterpart.
Environment variables and trust_env
By default HTTPX honors the HTTP_PROXY, HTTPS_PROXY and ALL_PROXY environment variables, which set the proxy for http, https, or all requests respectively. That is convenient in containers, but it can silently route traffic you did not intend to proxy. To ignore the environment, set trust_env=False:
import httpx
# Explicit proxy only; ignore HTTP_PROXY / HTTPS_PROXY / ALL_PROXY
client = httpx.Client(
proxy="http://u_4a9c:p_2X7q@proxy.mobileproxies.org:8000",
trust_env=False,
)The reverse is also useful: exporting ALL_PROXY once lets every HTTPX script in a job inherit the proxy without code changes.
Version cheat sheet
| HTTPX version | Proxy behavior (per official changelog) |
|---|---|
| 0.22.0 (Jan 2022) | SOCKS5 support added via the socksio package |
| 0.26.0 (Dec 2023) | proxy argument added; proxies deprecated |
| 0.28.0 (Nov 2024) | proxies removed; socks5h accepted as a proxy scheme |
Where a dedicated mobile IP fits
HTTPX does not care what kind of proxy sits behind the URL, but the target site does. A dedicated 4G/5G mobile proxy is a real SIM in a real modem, so your HTTPX traffic exits from a carrier ASN address that ordinary phone subscribers share. The honest tradeoffs: a dedicated slot is one IP (rotated on demand, not per request), mobile bandwidth is slower than datacenter lines, and it costs more per IP. In exchange you get an egress identity that is stable, geolocated to a real market (US, UK, France or Netherlands), and far more expensive for sites to block outright - the same reasons detection systems treat carrier ranges carefully, as explained in how websites detect proxies. For scraping workloads where session consistency beats raw request volume, that combination is usually worth it - see our web scraping solutions for the broader setup.
Sources
- • HTTPX docs - Proxies (proxy parameter, mounts, SOCKS, authentication)
- • HTTPX docs - API reference (Client and AsyncClient parameters)
- • HTTPX docs - Environment variables (HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, trust_env)
- • HTTPX changelog (0.22.0 SOCKS, 0.26.0 proxy added, 0.28.0 proxies removed)
- • HTTPX source - HTTPTransport and AsyncHTTPTransport (proxy parameter)
Related Guides
Developer Guide
aiohttp Proxy Setup
Async proxying with ProxyConnector
Developer Guide
Requests vs HTTPX vs aiohttp
Which HTTP client to pick for proxies
Developer Guide
Python requests SOCKS5 Proxy Example
Auth, DNS, retries and rotation with requests
Fundamentals
SOCKS5 vs HTTP Proxy
Which protocol fits which workload
Fundamentals
How Websites Detect Proxies
IP reputation, TLS and behavioral signals
Point HTTPX at a real mobile IP
Dedicated 4G/5G proxies on real carrier SIMs in the US, UK, France and Netherlands. HTTP and SOCKS5 endpoints that drop straight into the proxy= examples above.