# socks5 vs socks5h: what six clients actually send to the proxy

Source: https://ipvolt.com/blog/socks5-vs-socks5h
Markdown: https://ipvolt.com/blog/socks5-vs-socks5h.md

[Home](https://ipvolt.com/index.md) / [Blog](https://ipvolt.com/blog.md) / SOCKS5 vs SOCKS5h: What 6 Clients Actually Send (Tested)

Category: Comparison
Published: 2026-10-04
Updated: 2026-10-04
Author: ipvolt
Reading time: 7 minutes
Tags: proxies, troubleshooting

socks5:// does not mean local DNS in every client. We logged what curl, Requests, HTTPX, aiohttp, Playwright and Node send a SOCKS5 proxy for each scheme.

In curl, `socks5://` resolves the destination hostname on your machine and `socks5h://` hands the hostname to the proxy. Other clients do not follow that rule: in this test three of six sent the hostname with plain `socks5://`, and two of those refused `socks5h://` before connecting.

| Client (version tested) | `socks5://` sends the proxy | `socks5h://` |
| --- | --- | --- |
| curl 8.22.0 | an IPv4 address (resolved locally) | the hostname |
| Requests 2.34.2 + PySocks 1.7.1 | an IPv4 address (resolved locally) | the hostname |
| HTTPX 0.28.1 + socksio 1.0.0 | the hostname | the hostname |
| aiohttp 3.14.3 + aiohttp-socks 0.12.0 | the hostname | `ValueError`, no connection |
| Playwright 1.63.0 (Chromium 153.0.8010.12) | the hostname | `net::ERR_NO_SUPPORTED_PROXIES`, no connection |
| Node.js 24.20.0 + socks-proxy-agent 10.1.0 | an IPv4 address (resolved locally) | the hostname |

Each cell is what a logging SOCKS5 server on `127.0.0.1` received when the client was asked for `http://dnsprobe.example/`, a name that exists only in the local hosts file as `127.0.0.2`. "An IPv4 address" means the proxy saw `127.0.0.2` and never learned the name. "The hostname" means it received `dnsprobe.example` and would have to resolve it. Every client gave the same result for an `https://` target.

## Why the address type matters

A SOCKS5 request carries one destination address, and its `ATYP` byte says which kind: `X'01'` for IPv4, `X'03'` for a domain name, `X'04'` for IPv6. [RFC 1928, section 4](https://www.rfc-editor.org/rfc/rfc1928#section-4) A client that sends a domain name leaves DNS to the proxy. A client that sends an IP address has already done the lookup itself. That has three consequences:

1. **Your own resolver sees the lookup.** The DNS query for the destination goes to the resolver configured on your machine, outside the proxy connection. The Requests documentation says it directly: `socks5` "causes the DNS resolution to happen on the client, rather than on the proxy server". [Requests: SOCKS](https://requests.readthedocs.io/en/latest/user/advanced/#socks)
2. **The answer can be for the wrong place.** RFC 7871 notes that many authoritative nameservers "return different responses based on the perceived topological location of the user", judged from the address the query arrives from, which is normally your resolver. [RFC 7871, section 1](https://www.rfc-editor.org/rfc/rfc7871#section-1) A CDN hostname resolved locally can therefore point at a server chosen for your network, and the proxy connects to exactly that address. The exit IP is in the region you asked for, but the server it reached was picked for yours, so the content can be the wrong region's. This follows from how the request is built; the loopback test did not measure any CDN.
3. **Names only the proxy can resolve fail.** With a name that does not resolve locally, curl 8.22.0 stopped at `curl: (6) Could not resolve host: dnsprobe-missing.example`, Requests raised a `ConnectionError` containing `[Errno -2] Name or service not known`, and socks-proxy-agent failed with `getaddrinfo ENOTFOUND dnsprobe-missing.example`. None of them sent the proxy a request. With `socks5h://`, all three sent the name to the proxy.

The [HTTP vs SOCKS5 guide](/guides/http-vs-socks5-proxies) covers where DNS placement fits when you choose a proxy protocol. The rest of this post is about getting the scheme right per client.

## What to put in your config

| Client | Sends the hostname to the proxy | Avoid |
| --- | --- | --- |
| curl | `socks5h://` or `--socks5-hostname` | `socks5://` and `--socks5` resolve locally |
| Requests + PySocks | `socks5h://` | `socks5://` resolves locally |
| HTTPX + socksio | `socks5://` or `socks5h://` | `socks://` raises `ValueError` |
| aiohttp + aiohttp-socks | `socks5://` | `socks5h://` raises `ValueError` |
| Playwright Chromium | `socks5://` | `socks5h://` fails at navigation |
| Node socks-proxy-agent | `socks5h://` or `socks://` | `socks5://` resolves locally |

No single URL is right for all six. If one `PROXY_URL` setting feeds several tools, `socks5h://` breaks aiohttp-socks and Playwright, and `socks5://` makes curl, Requests and socks-proxy-agent resolve locally. Set the scheme per client.

The two clients that reject `socks5h://` fail at different moments. aiohttp-socks raises from `ProxyConnector.from_url()`, before any socket is opened:

```text
ValueError: Invalid scheme component: socks5h
```

Playwright accepts the proxy in `chromium.launch()` and fails on the first navigation, without contacting the proxy:

```text
page.goto: net::ERR_NO_SUPPORTED_PROXIES at http://dnsprobe.example/
```

Playwright's documentation gives `socks5://myproxy.com:3128` as the SOCKS form of the `server` option. [Playwright: proxy option](https://playwright.dev/docs/api/class-browsertype#browser-type-launch-option-proxy) The [Playwright proxy guide](/guides/playwright-proxy-setup) has the rest of the launch and context settings.

The bare `socks://` scheme is just as inconsistent. Chromium and socks-proxy-agent treated it as SOCKS5 and sent the hostname. curl opened with a SOCKS4 greeting (first byte `0x04`), which a SOCKS5-only server cannot answer. Requests raised `ValueError: Unable to determine SOCKS version from socks://127.0.0.1:11080`, HTTPX raised `ValueError: Unknown scheme for proxy URL URL('socks://127.0.0.1:11080')` and aiohttp-socks raised `ValueError: Invalid scheme component: socks`.

Two setup details from the Python side: Requests needs the `requests[socks]` extra and HTTPX needs `httpx[socks]`. Without them Requests raises `Missing dependencies for SOCKS support` and HTTPX raises an `ImportError` naming socksio; [the fix for both](/guides/fix-missing-dependencies-for-socks-support) also covers the case where the proxy URL comes from `ALL_PROXY`. The [Python Requests proxy guide](/guides/python-requests-proxy) and the [HTTPX async proxy guide](/guides/httpx-async-proxy) show complete client code.

This test passed the proxy URL to each client explicitly. If yours comes from `ALL_PROXY` or `HTTPS_PROXY`, first confirm which variable the client reads; the [proxy environment variables guide](/guides/proxy-environment-variables) lists that per client, and [NO_PROXY matching, tested](/blog/no-proxy-matching-tested) shows that bypass rules differ between clients as well.

## How to check your own setup

You do not need a proxy account to see what your client sends. Download [socks5_log.py](/downloads/socks5-vs-socks5h/socks5_log.py), a 42-line Python script with no dependencies. It completes the SOCKS5 greeting, prints the address type and address of each request, then answers "general SOCKS server failure", so nothing is forwarded.

Give your machine a test name that only the hosts file knows. Do not use `localhost`: Chromium sends requests for localhost names directly instead of through the proxy. [Chromium: implicit bypass rules](https://chromium.googlesource.com/chromium/src/+/HEAD/net/docs/proxy.md#Implicit-bypass-rules) Add this line to `/etc/hosts`:

```text
127.0.0.2 dnsprobe.example
```

Start the server in one terminal and send one request per scheme from another:

```sh
python3 socks5_log.py 1080
curl -x socks5://127.0.0.1:1080 http://dnsprobe.example/
curl -x socks5h://127.0.0.1:1080 http://dnsprobe.example/
```

With curl 8.18.0 the server printed:

```text
LISTENING 127.0.0.1:1080
CMD=1 IPv4 127.0.0.2 port=80
CMD=1 DOMAIN dnsprobe.example port=80
```

The first request arrived as an IP address, so curl resolved the name locally. The second arrived as a name. Both curl commands end with `curl: (97) cannot complete SOCKS5 connection to dnsprobe.example. (1)`, which is the server's refusal and is expected. Replace curl with your own client and proxy setting, and read the same two fields. If no line appears at all, the client rejected the scheme or bypassed the proxy.

## Remote DNS options besides the scheme

Some clients expose the same choice as an option, usually named `rdns`:

- **curl**: `--socks5-hostname host:port` behaved like `socks5h://` and `--socks5 host:port` like `socks5://` in the test. [curl man page](https://curl.se/docs/manpage.html#-x)
- **Requests**: there is no separate option. urllib3's SOCKS manager sets PySocks' `rdns` flag from the scheme: `False` for `socks5`, `True` for `socks5h`. [urllib3 source](https://github.com/urllib3/urllib3/blob/2.8.0/src/urllib3/contrib/socks.py) PySocks itself, used directly, takes `rdns` in `set_proxy()` and documents its default as `True`. [PySocks README](https://github.com/Anorov/PySocks)
- **aiohttp-socks**: `ProxyConnector` takes `rdns`, and its README notes "default is True for socks5". [aiohttp-socks README](https://github.com/romis2012/aiohttp-socks/blob/v0.12.0/README.md) In the test, `ProxyConnector.from_url("socks5://127.0.0.1:11080", rdns=False)` sent `127.0.0.2` and `rdns=True` sent the hostname.
- **HTTPX**: version 0.28.1 routes `socks5` and `socks5h` to the same SOCKS transport, so the scheme changes nothing. [HTTPX source](https://github.com/encode/httpx/blob/0.28.1/httpx/_transports/default.py)
- **socks-proxy-agent**: the scheme alone sets its `lookup` flag: `socks5` and `socks4` resolve locally, `socks5h`, `socks4a` and `socks` do not. [socks-proxy-agent source](https://github.com/TooTallNate/proxy-agents/blob/main/packages/socks-proxy-agent/src/index.ts)
- **Chromium**: no option. Its documentation says that for SOCKSv5 "name resolution is always done proxy side". [Chromium network docs](https://chromium.googlesource.com/chromium/src/+/HEAD/net/docs/proxy.md#SOCKSv5-proxy-scheme)

## Method and limits

Recorded on 4 October 2026 on Ubuntu 26.04 (x86_64) with Python 3.14.4 and Node.js 24.20.0. The versions are the ones in the first table, plus urllib3 2.8.0, httpcore 1.0.9, python-socks 3.1.1 and the `socks` package 2.8.10. curl 8.18.0 was run next to 8.22.0 and sent the same addresses. A name that resolves only to IPv6 arrived as an `IPv6` address from curl, Requests and socks-proxy-agent, and as a hostname from the other three.

The lab is one loopback server that refuses every request. It says nothing about any proxy service, about SOCKS5 with authentication or UDP, or about macOS, Windows, Firefox or WebKit. Library defaults change, so check the versions you run. The [lab archive](/downloads/socks5-vs-socks5h/socks5-vs-socks5h-lab.zip) contains the server, the six client scripts, the runner and a [README](/downloads/socks5-vs-socks5h/README.md); the raw output of all 62 cases is in [results.json](/downloads/socks5-vs-socks5h/results.json).

## Related

- [HTTP vs SOCKS5 proxies](/guides/http-vs-socks5-proxies), for choosing the protocol and deciding where DNS runs.
- [Proxy environment variables](/guides/proxy-environment-variables), for which variable each client reads.
- [NO_PROXY matching, tested](/blog/no-proxy-matching-tested), the same kind of test for bypass rules.
- [Playwright proxy setup](/guides/playwright-proxy-setup), [HTTPX async proxy](/guides/httpx-async-proxy) and [Python Requests proxy](/guides/python-requests-proxy), for full client configuration.

ipvolt is building proxy infrastructure for developers and agents. [Join the waitlist](https://ipvolt.com/#waitlist-hero) for one email when access opens.

## Sources

- [curl man page: --proxy (socks5:// and socks5h://)](https://curl.se/docs/manpage.html#-x)
- [RFC 1928: SOCKS Protocol Version 5, section 4 (address types)](https://www.rfc-editor.org/rfc/rfc1928#section-4)
- [Requests: SOCKS proxies](https://requests.readthedocs.io/en/latest/user/advanced/#socks)
- [urllib3 2.8.0 source: contrib/socks.py (scheme to rdns)](https://github.com/urllib3/urllib3/blob/2.8.0/src/urllib3/contrib/socks.py)
- [PySocks README: set_proxy and rdns](https://github.com/Anorov/PySocks)
- [HTTPX: SOCKS proxies](https://www.python-httpx.org/advanced/proxies/#socks)
- [HTTPX 0.28.1 source: proxy schemes in _transports/default.py](https://github.com/encode/httpx/blob/0.28.1/httpx/_transports/default.py)
- [aiohttp-socks 0.12.0 README: ProxyConnector and rdns](https://github.com/romis2012/aiohttp-socks/blob/v0.12.0/README.md)
- [python-socks 3.1.1 source: parse_proxy_url schemes](https://github.com/romis2012/python-socks/blob/v3.1.1/python_socks/_helpers.py)
- [socks-proxy-agent source: parseSocksURL](https://github.com/TooTallNate/proxy-agents/blob/main/packages/socks-proxy-agent/src/index.ts)
- [Chromium network docs: SOCKSv5 proxy scheme and implicit bypass rules](https://chromium.googlesource.com/chromium/src/+/HEAD/net/docs/proxy.md#SOCKSv5-proxy-scheme)
- [Playwright: browserType.launch proxy option](https://playwright.dev/docs/api/class-browsertype#browser-type-launch-option-proxy)
- [RFC 7871: Client Subnet in DNS Queries, section 1](https://www.rfc-editor.org/rfc/rfc7871#section-1)

## Know when ipvolt access opens.

ipvolt is in development. Leave your email and we’ll notify you once when access opens.

Consent: One email when access opens. Nothing else.

[Notify me](https://ipvolt.com/blog/socks5-vs-socks5h#waitlist-blog-end). Use the email form on this page to join the interest list.

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

## Related posts

- [Japanese price parsing: yen, width and tax labels](https://ipvolt.com/blog/japanese-price-parsing.md) (Analysis, Oct 6, 2026, 6 min read): Parse Japanese price fields without losing currency or tax basis. Run a tested Python fixture for fullwidth yen, mixed prices and deliberate review cases.
- [CONNECT tunnel failed, response 403: Why AI Agents Hit It](https://ipvolt.com/blog/connect-tunnel-failed-403.md) (Analysis, Oct 2, 2026, 8 min read): Your agent's curl fails with CONNECT tunnel failed, response 403 while other hosts work. The proxy refused the tunnel. How to tell who blocked it, and the fix.
- [Betting odds API vs scraping: a cost worksheet](https://ipvolt.com/blog/betting-odds-api-vs-scraping.md) (Comparison, Sep 28, 2026, 7 min read): Compare odds APIs and authorized scraping by coverage, freshness and modeled collection cost. Use an editable worksheet with clear workload assumptions.

## Related guides

- [HTTP vs SOCKS5 proxies: choose by the connection](https://ipvolt.com/guides/http-vs-socks5-proxies.md): Compare HTTP CONNECT and SOCKS5, understand DNS placement and TLS boundaries, and select a protocol your actual client and provider support.
- [Proxy environment variables: HTTP_PROXY and NO_PROXY](https://ipvolt.com/guides/proxy-environment-variables.md): Diagnose HTTP_PROXY, HTTPS_PROXY, ALL_PROXY and NO_PROXY routing differences in curl, Python Requests and Node.js with an isolated local check.
- [Configure a proxy in Playwright](https://ipvolt.com/guides/playwright-proxy-setup.md): Set an authenticated HTTP proxy for a Playwright browser context, keep test state isolated, and diagnose navigation separately from subresources.

## About ipvolt

Technical analysis from the ipvolt team.

ipvolt access is not open yet.
