# httpx Proxy: Setup, Auth and the proxies= Fix

> Set a proxy in Python httpx with proxy=, fix the proxies= TypeError on httpx 0.28, route per domain with mounts, add SOCKS5 and rotate exits safely.

[Home](https://quanticdata.io/)/[Blog](https://quanticdata.io/blog/)/httpx Proxy: Setup, Auth and the proxies= Fix

# httpx Proxy: Setup, Auth and the proxies= Fix

ProxiesSep 10, 2026·8 min read·By [Aldo Morese](https://quanticdata.io/about/), founder of QuanticData

httpx routes a request through a proxy, by forwarding or by tunnelling

On this page [The httpx proxy setting in one line](/blog/httpx-proxy/#the-httpx-proxy-setting-in-one-line) [proxies= vs proxy=: what changed, and in which version](/blog/httpx-proxy/#proxies-vs-proxy-what-changed-and-in-which-version) [Authentication, and what the proxy actually receives](/blog/httpx-proxy/#authentication-and-what-the-proxy-actually-receives) [Per-domain routing with mounts, and the per-request habit that breaks](/blog/httpx-proxy/#per-domain-routing-with-mounts-and-the-per-request-habit-tha) [SOCKS5 with httpx](/blog/httpx-proxy/#socks5-with-httpx) [Environment variables and how to ignore them](/blog/httpx-proxy/#environment-variables-and-how-to-ignore-them) [The errors you will actually hit](/blog/httpx-proxy/#the-errors-you-will-actually-hit) [When the proxy layer stops being the interesting part](/blog/httpx-proxy/#when-the-proxy-layer-stops-being-the-interesting-part)

To use a proxy with Python httpx, pass a proxy URL to the client as `proxy=`, not `proxies=`. The plural form was deprecated in httpx 0.26 and removed in 0.28, which is why so much working code suddenly raises `TypeError: Client.__init__() got an unexpected keyword argument 'proxies'`.

## The httpx proxy setting in one line

httpx takes the proxy as a single URL, applied to every request the client makes:

```
import httpx

with httpx.Client(proxy="http://198.51.100.10:8080", timeout=10) as client:
    r = client.get("https://api.ipify.org?format=json")
    print(r.text)
```

The same argument exists on the top-level helpers and on the async client:

```
httpx.get("https://example.com/", proxy="http://198.51.100.10:8080")

async with httpx.AsyncClient(proxy="http://198.51.100.10:8080") as client:
    r = await client.get("https://example.com/")
```

One URL covers both schemes. You do not need a separate entry for `https://`, and the proxy URL itself should almost always start with `http://` even when the target is HTTPS, because the connection to the proxy is plain HTTP before the tunnel is opened. If the printed IP is the proxy exit rather than your own address, the route works. The [proxy tester](https://quanticdata.io/tools/proxy-tester/) runs the same check in the browser when you want a second opinion without writing code.

## proxies= vs proxy=: what changed, and in which version

The rename is the single biggest source of httpx proxy questions, and the answer depends on the version you have installed. We installed five httpx releases in isolated virtualenvs on Python 3.13.6 with OpenSSL 3.6.3, and called each form on 10 September 2026. This is what each version does:

| httpx | `Client(proxies=...)` | `Client(proxy=...)` | `mounts=` with a string |
| --- | --- | --- | --- |
| 0.23.3 | accepted, silent | TypeError, unexpected keyword argument 'proxy' | AttributeError, needs `httpx.Proxy(...)` |
| 0.25.2 | accepted, silent | TypeError, unexpected keyword argument 'proxy' | AttributeError, needs `httpx.Proxy(...)` |
| 0.26.0 | accepted, DeprecationWarning | accepted | accepted |
| 0.27.2 | accepted, DeprecationWarning | accepted | accepted |
| 0.28.1 | TypeError, unexpected keyword argument 'proxies' | accepted | accepted |

The deprecation text in 0.26 and 0.27 is exact and worth grepping your logs for: *The 'proxies' argument is now deprecated. Use 'proxy' or 'mounts' instead.* Because it is a `DeprecationWarning`, Python hides it by default outside `__main__`, so most projects never saw it and met the removal as a hard failure two releases later.

Three practical consequences follow from that table.

- **If you own the code**, replace `proxies="http://..."` with `proxy="http://..."` and require `httpx>=0.26`. A dict of per-scheme proxies becomes `mounts`, covered below.

- **If a dependency raises it**, the traceback usually points at a library that still passes `proxies` down into httpx, and the file in the traceback is not yours. Upgrading that library is the fix; pinning `httpx<0.28` only buys time.

- **A dict passed to the new argument** is not a shortcut. On 0.26 and 0.27 `proxy={"http://": ...}` was accepted and routed anyway; on 0.28.1 the same line raises `AttributeError: 'dict' object has no attribute 'url'`, which reads like a bug in httpx and is not one.

## Authentication, and what the proxy actually receives

Credentials go in the userinfo part of the proxy URL. There is no separate auth argument:

```
proxy = "http://USER:PASSWORD@proxy.example.net:8080"

with httpx.Client(proxy=proxy, timeout=10) as client:
    r = client.get("https://example.com/")
```

Percent-encode any of `@ : / #` that appear in the password, or the URL parser will split the host in the wrong place. That single mistake is behind most [407 Proxy Authentication Required](https://quanticdata.io/blog/how-to-fix-407-proxy-authentication-required/) reports.

To see what httpx sends rather than guess, we pointed it at a socket-level stub that logs the first bytes of every connection. Same client, same credentials, two targets:

| Target URL | First line the proxy sees | Headers on that line |
| --- | --- | --- |
| `http://example.com/ip` | `GET http://example.com/ip HTTP/1.1` | Host, Proxy-Authorization, Accept, Accept-Encoding, Connection, User-Agent |
| `https://example.com/ip` | `CONNECT example.com:443 HTTP/1.1` | Host, Accept, Proxy-Authorization only |

Two things are worth taking away. First, an HTTP target is forwarded in absolute form and the proxy reads the whole request, path and headers included; an HTTPS target is tunnelled and the proxy learns only the hostname and port. Second, the forwarded request carries `User-Agent: python-httpx/0.28.1` unless you override it. A default client announces the library and its exact version to every site you touch, which is a fingerprint no proxy quality can hide. Set a realistic `headers=` dict on the client before you blame the IP pool.

## Per-domain routing with mounts, and the per-request habit that breaks

httpx has no per-request proxy. `client.get(url, proxy=...)` raises `TypeError: Client.get() got an unexpected keyword argument 'proxy'` on every version we tested, 0.23.3 through 0.28.1. This is the main behavioural gap for people arriving from requests, where the proxy is a per-call argument. In httpx the proxy is a property of the client, so routing decisions are made when you build it:

```
transport = httpx.HTTPTransport(proxy="http://198.51.100.10:8080")

client = httpx.Client(mounts={
    "all://internal.example.com": None,   # never proxied
    "all://": transport,                  # everything else
})
```

A `None` mount means direct. We verified the split against the stub: a request to the mounted host arrived at the proxy, a request to the excluded host went straight out and returned the real site's 404. Note that on 0.23 and 0.25 the transport wants `httpx.Proxy("http://...")` rather than a bare string, which is a second reason those versions are painful to support.

Rotation follows from the same rule. Because a client owns its proxy, you rotate by picking a client, not by picking a URL:

```
import itertools, httpx

# credentials go in each URL, exactly as shown above
exits = ["http://gate.example.net:8080?session=a",
         "http://gate.example.net:8080?session=b"]
clients = itertools.cycle([httpx.Client(proxy=p, timeout=15) for p in exits])

for url in urls:
    r = next(clients).get(url)
```

Building one client per request instead throws away the connection pool and, with it, most of the speed advantage of httpx. If what you actually want is a different IP per request, take it from the endpoint rather than from your code: [rotating proxies](https://quanticdata.io/rotating-proxies/) give a fresh exit on every request through one host and one credential, and sticky sessions when a flow has to keep the same IP.

## SOCKS5 with httpx

SOCKS support is an optional extra. Without it, the failure is explicit rather than mysterious: `ImportError: Using SOCKS proxy, but the 'socksio' package is not installed.` Install it and the same `proxy=` argument takes a SOCKS URL:

```
pip install "httpx[socks]"
```

```
with httpx.Client(proxy="socks5://USER:PASSWORD@proxy.example.net:1080") as client:
    r = client.get("https://example.com/")
```

A detail that trips people migrating from requests: there, `socks5` resolves DNS locally and `socks5h` resolves it at the proxy, and choosing wrong leaks your resolver or breaks internal hostnames. We ran both schemes against a SOCKS5 stub that decodes the connect request. Both sent address type 3 with the literal hostname `example.com` and port 443. In httpx, `socks5` and `socks5h` behave the same way, and the name is always resolved at the exit. If you need SOCKS on a commercial pool, [SOCKS5 proxies](https://quanticdata.io/socks5-proxies/) expose the same credentials over both protocols, so the switch is a scheme change and nothing else.

## Environment variables and how to ignore them

httpx reads `HTTP_PROXY`, `HTTPS_PROXY`, `ALL_PROXY` and `NO_PROXY` by default. We confirmed both halves against the stub: with `HTTP_PROXY` set, a plain `httpx.Client()` routed through it with no argument at all, and adding `NO_PROXY=example.com` sent the next request direct.

That default is convenient on a laptop and dangerous in production, where a stray variable in a container image silently reroutes traffic. Two ways to be explicit:

- `httpx.Client(trust_env=False)` ignores the environment entirely.

- `httpx.Client(proxy=...)` takes precedence over the variables, so an explicit proxy is never overridden by one.

When a request behaves differently inside a job runner than on your machine, print the environment before you debug the code. It is the most common cause of a "proxy not working" report that turns out to be a proxy working perfectly, for the wrong destination.

## The errors you will actually hit

| Message | What it means |
| --- | --- |
| `TypeError: ... unexpected keyword argument 'proxies'` | httpx 0.28 or newer. Rename to `proxy`, or upgrade the library that passes it. |
| `TypeError: ... unexpected keyword argument 'proxy'` | httpx 0.25 or older. Upgrade, or use `proxies` on that version. |
| `AttributeError: 'dict' object has no attribute 'url'` | A dict was passed to `proxy=`. Use `mounts=` for per-scheme routing. |
| `ImportError: ... 'socksio' package is not installed` | A SOCKS URL without the extra. Install `httpx[socks]`. |
| `httpx.ProxyError` | The proxy answered and refused: bad credentials, blocked target, exhausted plan. |
| `httpx.ConnectError` | The proxy never answered: wrong port, wrong scheme, firewall, dead exit. |
| `httpx.ConnectTimeout` on HTTPS only | The proxy accepts connections but does not honour CONNECT. |

The same taxonomy applies one library over: if you also maintain requests code, the exception classes differ but the causes do not, and the guide to [requests.exceptions.ProxyError](https://quanticdata.io/blog/requests-exceptions-proxyerror/) maps them the same way.

## When the proxy layer stops being the interesting part

Everything above is plumbing. It matters right up to the point where the target starts returning challenges instead of pages, and then no amount of argument-renaming helps. At that point you are choosing between running the retry, header and fingerprint logic yourself on top of [residential proxies](https://quanticdata.io/residential-proxies/), or handing the whole fetch to an API and paying only for the pages that come back. The [web scraping API](https://quanticdata.io/web-scraping-api/) bills successful responses from $0.0002 per page and returns Markdown or structured JSON, which removes the client-side proxy question entirely; residential exits start at $0.80 per GB if you would rather keep the loop in your own code. Both sit on the same $2 of free monthly usage, which is enough to test either path before deciding.

### Sources & further reading

- [HTTPX documentation: Proxies](https://www.python-httpx.org/advanced/proxies/)

- [HTTPX documentation: Environment Variables](https://www.python-httpx.org/environment_variables/)

- [HTTPX changelog (encode/httpx)](https://github.com/encode/httpx/blob/master/CHANGELOG.md)

- [OpenAI community thread on the removed proxies argument](https://community.openai.com/t/error-with-openai-1-56-0-client-init-got-an-unexpected-keyword-argument-proxies/1040332)

- [httpx-socks: SOCKS transports for httpx](https://github.com/romis2012/httpx-socks)

- [RFC 9110, section on the CONNECT method](https://www.rfc-editor.org/rfc/rfc9110#name-connect)

## FAQ

Quick answers on httpx proxy.

[Something else? Ask us →](mailto:hello@quanticdata.io)

### How do I fix "Client.__init__() got an unexpected keyword argument proxies"?

You are on httpx 0.28 or newer, where the `proxies` argument was removed. In your own code, rename it to `proxy` and pass a single URL string. If the traceback points inside another package, that package is passing `proxies` to httpx: upgrade it. Pinning `httpx<0.28` works but leaves you on an old release.

### Does httpx support a different proxy per request?

No. `client.get(url, proxy=...)` raises a TypeError on every version we tested, from 0.23.3 to 0.28.1. The proxy belongs to the client. Keep one client per exit and choose the client, or use `mounts` to route by scheme and hostname.

### Should the proxy URL use http:// or https:// for an HTTPS target?

Use `http://`. The scheme describes how you talk to the proxy, not how the proxy talks to the site. httpx opens a plain connection, sends CONNECT, and performs the TLS handshake with the target through the tunnel, so the target is still reached over HTTPS.

### What is the difference between socks5 and socks5h in httpx?

Nothing, in practice. Against a SOCKS5 stub that decodes the connect request, both schemes sent the hostname to the proxy rather than a resolved address, so DNS happens at the exit either way. Both need the `httpx[socks]` extra.

### Why does my request go through a proxy I never configured?

httpx trusts the environment by default, so `HTTP_PROXY`, `HTTPS_PROXY` or `ALL_PROXY` set in a shell, image or CI runner will be used. Pass `trust_env=False` to ignore them, or set `NO_PROXY` for the hosts that must go direct.

### How do I check the proxy is really being used?

Request an IP echo endpoint through the client and compare the answer with your own address. If they match, the request went direct. A response that shows the exit IP confirms the route, and is the fastest way to separate a configuration problem from a blocking problem.

## Proxies that behave the same on every httpx version

One endpoint, HTTP and SOCKS5 on the same credentials, rotating or sticky exits, and a scraping API for the pages that fight back. Every account gets $2 of free usage each month.

[Start free — $2/month included](https://quanticdata.io/signup/)[Explore Residential Proxies from $0.80/GB](https://quanticdata.io/residential-proxies/)

## Related reading

[Proxies curl Proxy: Setup, Auth, SOCKS5 and Errors One flag sets a proxy in curl. The rest of the work is authentication, SOCKS5 versus SOCKS5h, HTTPS tunnelling, and reading the exit code when it fails. Read →](https://quanticdata.io/blog/how-to-use-a-proxy-with-curl/) [Proxies Playwright Proxy: Setup, Auth and Bandwidth Playwright takes a proxy as an object, not a URL, and a browser pays for the whole page rather than the HTML. Per-context exits, the auth rules, the net errors, and measured bytes for seven real pages. Read →](https://quanticdata.io/blog/playwright-proxy/) [Proxies Shadowrocket Proxy Setup: The iPhone Guide Shadowrocket is a routing engine, not a VPN subscription. The add-server screen takes two minutes; the rules decide whether it is worth the money. Setup, config syntax, and measured exits. Read →](https://quanticdata.io/blog/shadowrocket-proxy-setup/)

## Also on this site

Quantic**Data**

Residential proxies & web data APIs for AI.

#### Proxies

- [Residential Basic](https://quanticdata.io/residential-proxies/#basic)

- [Residential Premium](https://quanticdata.io/residential-proxies/#plans)

- [Cheap Residential](https://quanticdata.io/cheap-residential-proxies/)

- [Mobile Proxies](https://quanticdata.io/mobile-proxies/)

- [Datacenter Proxies](https://quanticdata.io/datacenter-proxies/)

- [ISP Proxies](https://quanticdata.io/isp-proxies/)

- [Web Unlocker proxy](https://quanticdata.io/web-unlocker/)

- [Enterprise solutions](https://quanticdata.io/enterprise-solutions/)

- [Rotating Proxies](https://quanticdata.io/rotating-proxies/)

- [Sneaker Proxies](https://quanticdata.io/sneaker-proxies/)

- [SOCKS5 Proxies](https://quanticdata.io/socks5-proxies/)

- [IPv6 Proxies](https://quanticdata.io/ipv6-proxies/)

- [Proxy locations](https://quanticdata.io/proxies/)

#### Data APIs

- [MCP Server](https://quanticdata.io/mcp-server/)

- [Web Scraper API](https://quanticdata.io/web-scraping-api/)

- [SERP API](https://quanticdata.io/serp-api/)

- [Collectors](https://quanticdata.io/collectors/)

- [Web Data for AI](https://quanticdata.io/web-data-api-for-ai/)

- [Quantic AI](https://quanticdata.io/ai-web-scraping-service/)

- [Crawl & Map](https://quanticdata.io/crawl-map/)

- [SEO Audit](https://quanticdata.io/seo-audit/)

#### Use cases

- [Company data](https://quanticdata.io/scrape-company-data/)

- [Price monitoring](https://quanticdata.io/competitor-price-monitoring/)

- [Market research](https://quanticdata.io/market-research-data/)

- [Real estate data](https://quanticdata.io/real-estate-data-scraping/)

- [Scrape job postings](https://quanticdata.io/scrape-job-postings/)

#### Company

- [Documentation](https://quanticdata.io/docs/)

- [Blog](https://quanticdata.io/blog/)

- [Free tools](https://quanticdata.io/tools/)

- [Partners](https://quanticdata.io/partners/)

- [Affiliates](https://quanticdata.io/affiliates/)

- [About](https://quanticdata.io/about/)

- [Alternatives](https://quanticdata.io/alternatives/)

- [Pricing](https://quanticdata.io/pricing/)

- [FAQ](https://quanticdata.io/#faq)

- [For AI agents](https://quanticdata.io/#ai)

#### Free tools

- [All tools](https://quanticdata.io/tools/)

- [Website to Markdown](https://quanticdata.io/tools/website-to-markdown/)

- [PDF to Markdown](https://quanticdata.io/tools/pdf-to-markdown/)

- [WAF detector](https://quanticdata.io/tools/waf-detector/)

- [AI visibility audit](https://quanticdata.io/tools/ai-visibility-audit/)

- [AI crawler checker](https://quanticdata.io/tools/ai-crawler-checker/)

- [robots.txt tester](https://quanticdata.io/tools/robots-txt-tester/)

- [robots.txt generator](https://quanticdata.io/tools/robots-txt-generator/)

- [User agent](https://quanticdata.io/tools/user-agent/)

- [cURL converter](https://quanticdata.io/tools/curl-converter/)

- [Proxy tester](https://quanticdata.io/tools/proxy-tester/)

© 2026 QuanticData ·

- [quanticdata.io](https://quanticdata.io/)

·

- [Terms](https://quanticdata.io/terms/)

·

- [Privacy](https://quanticdata.io/privacy/)

If you are an AI agent:

- [llms.txt](https://quanticdata.io/llms.txt)

·

- [llms-full.txt](https://quanticdata.io/llms-full.txt)

---

Source: https://quanticdata.io/blog/httpx-proxy/ · Site index for AI: https://quanticdata.io/llms.txt · Full dump: https://quanticdata.io/llms-full.txt
