SOCKS5 vs HTTP proxy
SOCKS5 and HTTP proxies solve different problems. Handshake bytes, address types, UDP ASSOCIATE, socks5 vs socks5h DNS leaks, and a decision table.
Key points
- SOCKS5 (RFC 1928) is a per-connection binary handshake that relays TCP or UDP without understanding the payload; an HTTP proxy parses every request and can cache, filter and authenticate it.
socks5://in curl resolves the hostname on the client;socks5h://sends the name to the proxy. The same split exists assocks4://versussocks4a://.- Only SOCKS5 can carry UDP, via
UDP ASSOCIATE, and the association dies the moment the controlling TCP connection closes. - SOCKS5 authentication happens once per TCP connection, so it cannot be re-challenged mid-stream the way an HTTP
407can.
A SOCKS5 proxy is a transport-layer relay: the client performs a short binary handshake (RFC 1928), names a destination host and port, and from then on the proxy copies bytes without parsing them. An HTTP proxy is an application-layer intermediary: it parses each request line and header block, so it can cache, rewrite, filter on URL, and authenticate every request independently. Choose SOCKS5 when the traffic is not HTTP or you need UDP; choose an HTTP proxy when you want per-URL policy, caching, or auditable per-request logs.
The distinction blurs for HTTPS, because an HTTP proxy handling https:// also becomes a blind byte relay after the CONNECT handshake. At that point the only real differences are the handshake format, the auth model, and UDP support.
Feature comparison#
| Property | SOCKS5 (RFC 1928) | HTTP proxy (absolute-form) | HTTP proxy (CONNECT) |
|---|---|---|---|
| Handshake | Binary, 2 round trips minimum | None, the request itself is the ask | One request/response, then opaque |
| Understands payload | No | Yes, full HTTP semantics | No, after the 2xx |
| Caching | Impossible | Yes, per RFC 9111 | Impossible |
| Per-URL filtering | No, host and port only | Yes | Host and port, plus SNI |
| Non-HTTP protocols | Yes, any TCP | No | Yes, subject to port ACLs |
| UDP | Yes, UDP ASSOCIATE | No | No |
| Inbound connections | Yes, BIND | No | No |
| Auth granularity | Once per TCP connection | Per request | Per tunnel setup |
| Auth schemes | No-auth, GSSAPI, username/password | Basic, Digest, Negotiate, NTLM, Bearer | Same as absolute-form |
| Adds identifying headers | Never | Via, X-Forwarded-For if configured | Never inside the tunnel |
| Remote DNS | Optional, via address type 0x03 | Always (proxy resolves) | Always (proxy resolves) |
| Env var convention | ALL_PROXY | HTTP_PROXY / HTTPS_PROXY | HTTPS_PROXY |
The "adds identifying headers" row matters more than it looks. A SOCKS5 proxy is invisible to the application protocol, so an origin sees only the proxy's source IP and there is no header for it to trust or distrust. An HTTP proxy can, and by policy often must, insert forwarding headers, which is where the whole trusted proxies problem originates.
The SOCKS5 handshake, byte by byte#
SOCKS5 is three short exchanges over TCP: method negotiation, optional authentication subnegotiation, then the request.
1. Method negotiation#
C -> P 05 02 00 02
| | | +-- method 0x02 USERNAME/PASSWORD
| | +----- method 0x00 NO AUTHENTICATION REQUIRED
| +-------- NMETHODS = 2
+----------- VER = 0x05
P -> C 05 02
| +-- selected METHOD = 0x02
+----- VER = 0x05Defined method identifiers: 0x00 no authentication, 0x01 GSSAPI (RFC 1961), 0x02 username/password (RFC 1929), 0x03 to 0x7F IANA assigned, 0x80 to 0xFE private use, and 0xFF meaning no acceptable methods. A server that answers 05 FF has rejected every method the client offered and the client must close the connection.
2. Username/password subnegotiation (RFC 1929)#
C -> P 01 05 61 6c 69 63 65 06 73 33 63 72 33 74
| | +-- "alice" | +-- "s3cr3t"
| +----- ULEN = 5 +----- PLEN = 6
+-------- VER = 0x01 (subnegotiation version, NOT 0x05)
P -> C 01 00
+-- STATUS 0x00 = success (anything else = failure, close)The version octet here is 0x01, the version of the username/password subnegotiation, not the SOCKS version. Implementations that hardcode 0x05 here fail against strict servers. Credentials are sent in cleartext, exactly as with HTTP Basic proxy authentication, so SOCKS5 auth over an untrusted network is only safe when the SOCKS connection is itself tunnelled (for example over SSH).
3. The request#
C -> P 05 01 00 03 0b 65 78 61 6d 70 6c 65 2e 63 6f 6d 01 bb
| | | | | +-- "example.com" +-- port 443
| | | | +----- length = 11
| | | +-------- ATYP = 0x03 (domain name)
| | +----------- RSV = 0x00
| +-------------- CMD = 0x01 (CONNECT)
+----------------- VER = 0x05
P -> C 05 00 00 01 00 00 00 00 00 00
| | | | +-- BND.ADDR 0.0.0.0 +-- BND.PORT 0
| | | +----- ATYP = 0x01 (IPv4)
| | +-------- RSV
| +----------- REP = 0x00 (succeeded)
+-------------- VERAfter 05 00 ... the connection is a byte pipe, identical in behaviour to a post-200 CONNECT tunnel.
Address types and reply codes#
| ATYP | Address form | Length encoding |
|---|---|---|
0x01 | IPv4 | Fixed 4 octets |
0x03 | Fully qualified domain name | One length octet, then that many octets, no trailing NUL |
0x04 | IPv6 | Fixed 16 octets |
| REP | Meaning | What the engineer sees |
|---|---|---|
0x00 | Succeeded | Traffic flows |
0x01 | General SOCKS server failure | Usually the proxy's own error, check its logs |
0x02 | Connection not allowed by ruleset | ACL denial, the SOCKS analogue of HTTP 403 |
0x03 | Network unreachable | Routing problem at the proxy |
0x04 | Host unreachable | Includes DNS failure when the proxy resolves the name |
0x05 | Connection refused | Origin sent RST, analogue of HTTP 502 |
0x06 | TTL expired | Rare, path problem |
0x07 | Command not supported | BIND or UDP ASSOCIATE disabled |
0x08 | Address type not supported | Typically IPv6 or domain names disabled |
Note what is missing: there is no reply code for "authenticate now". SOCKS5 authenticates once, before the request, and cannot re-challenge. HTTP's 407 can be issued at any point in a connection's life.
CONNECT, BIND and UDP ASSOCIATE#
CONNECT(0x01) is the command every implementation supports: open a TCP connection toDST.ADDR:DST.PORT.BIND(0x02) asks the proxy to listen for one inbound connection on the client's behalf, for protocols that reverse the connection direction (classically FTP in active mode). It produces two replies: the first carries the address and port the proxy is listening on, the second is sent when a peer actually connects. Most modern SOCKS servers reject it with0x07.UDP ASSOCIATE(0x03) requests a UDP relay.DST.ADDR/DST.PORTin the request declare the address the client will send datagrams from (zeros if not yet known), and the reply'sBND.ADDR/BND.PORTis where the client sends its datagrams.
Each relayed datagram is prefixed with a small header: two reserved octets 00 00, one FRAG octet, then ATYP, DST.ADDR and DST.PORT, then the payload. FRAG supports reassembly of oversized datagrams and is set to 0x00 for standalone datagrams; many servers refuse anything else, so treat fragmentation as unavailable in practice.
The DNS trap: socks5 versus socks5h#
SOCKS5's ATYP = 0x03 lets the client hand over a hostname and let the proxy resolve it. Whether a given client does that is a per-client decision, and the naming is inconsistent.
| Client | Local resolution | Remote resolution | Default |
|---|---|---|---|
curl --proxy | socks5://, socks4:// | socks5h://, socks4a:// | Whatever the scheme says; --socks5-hostname forces remote |
| Chromium | socks4:// (SOCKS4 has no domain address type) | socks5://, which sends ATYP 0x03 | Remote for SOCKS5 |
| Firefox | network.proxy.socks_remote_dns = false | network.proxy.socks_remote_dns = true | Has varied by release; check the pref in the version you run |
OpenSSH -D | Not offered | Always sends the hostname | Remote |
Python PySocks | socks5 with rdns=False | rdns=True | Remote (rdns defaults to true) |
The obvious hazard is the DNS leak: with socks5://, curl asks the local resolver for internal.corp.example before connecting, so the name (and therefore your browsing target) is visible to whoever runs that resolver, which defeats the point of using an anonymising or geo-shifting proxy. Anyone routing through a local Tor SOCKS listener must use socks5h://.
When to choose which#
| Situation | Choose | Why |
|---|---|---|
| Non-HTTP TCP (SSH, SMTP, database clients, game protocols) | SOCKS5 | No port ACL culture, no HTTP framing to satisfy |
| You need UDP (DNS, QUIC, WebRTC media, syslog) | SOCKS5 | UDP ASSOCIATE is the only option; HTTP proxies have no UDP path before CONNECT-UDP |
| Enterprise egress control with URL categories and audit logs | HTTP proxy | Per-request logging and per-URL policy exist only at the HTTP layer |
| You want shared caching of public assets | HTTP proxy | RFC 9111 caching needs a parsed request/response; see caching proxies |
| Kerberos or NTLM single sign-on against a corporate directory | HTTP proxy | SOCKS5's GSSAPI method exists but tooling support is thin; HTTP Negotiate is universally implemented |
| Ad hoc developer tunnel through a bastion | SOCKS5 via ssh -D | One command, no server-side proxy software, remote DNS by default |
| Traffic must be indistinguishable from normal web traffic | HTTP proxy over TLS, or MASQUE | SOCKS5 on a non-standard port is trivially fingerprinted |
Client library only reads HTTP_PROXY/HTTPS_PROXY | HTTP proxy | Many SDKs ignore ALL_PROXY entirely |
The last row is the one that decides real migrations. SOCKS5 support in language ecosystems is uneven: Go's net/http does not read ALL_PROXY through http.ProxyFromEnvironment and needs golang.org/x/net/proxy wired in manually, while curl, Python requests (with PySocks installed) and the JVM all handle it, each with different bypass-list behaviour. Check the mismatches with the no_proxy tester before assuming a bypass list applies to SOCKS traffic at all.
Worked example: same destination, both ways#
# SOCKS5 with remote DNS. The proxy resolves example.com.
curl -v --proxy socks5h://127.0.0.1:1080 https://example.com/
# HTTP proxy. curl issues CONNECT example.com:443, the proxy resolves the name.
curl -v --proxy http://proxy.corp.example:3128 https://example.com/The observable difference on the wire at the client is 05 01 00 03 0b 65 78 ... (18 octets) versus a CONNECT request block of roughly 100 octets of ASCII. The observable difference at the proxy is that the SOCKS server logs one line with a host and port, whereas the HTTP proxy can log the tunnel plus Proxy-Authorization identity, User-Agent and Via for any plain http:// requests on the same connection. More detail on driving both from the command line is in curl through a proxy.
Failure modes#
curl: (97) Can't complete SOCKS5 connection to example.com with reply code 0x04. The proxy resolved the name and failed. If you used socks5h://, the proxy's resolver is the problem; if you used socks5://, your own resolver already succeeded and the failure is routing at the proxy.
Silence after UDP ASSOCIATE. Almost always the closed control connection described above, or a NAT in front of the client rewriting the source port so the proxy drops datagrams that do not match the association.
0x07 Command not supported on BIND. Expected. Do not design around BIND; use an explicit reverse tunnel instead.
Proxy auth prompts that never appear. SOCKS5 has no challenge mechanism, so a client that was not configured with credentials up front simply offers 0x00 and receives 05 FF, which surfaces as a generic connection failure rather than an authentication error.
Applications that "support SOCKS" but resolve locally. Symptom: the proxy works for IP literals and fails for internal hostnames, or vice versa. Check the client's remote-DNS setting before blaming the proxy.
Mixed environments where only HTTP is proxied. Setting HTTPS_PROXY does not route a database driver's TCP connection anywhere. If non-HTTP traffic must egress through a control point, SOCKS5 or a transparent interception layer is required; see transparent and intercepting proxies.
Frequently asked questions#
What is the difference between SOCKS5 and an HTTP proxy?#
SOCKS5 relays TCP or UDP at the transport layer after a short binary handshake and never inspects the payload. An HTTP proxy parses HTTP requests, so it can cache responses, filter by URL, insert forwarding headers and authenticate each request. For HTTPS traffic an HTTP proxy also becomes a blind relay, but only after a CONNECT exchange.
What does the "h" in socks5h mean?#
It means hostname resolution happens at the proxy. With socks5h:// curl sends the destination name in the SOCKS request using address type 0x03; with socks5:// curl resolves the name locally and sends an IP address. The same distinction exists between socks4:// and socks4a://.
Is SOCKS5 faster than an HTTP proxy?#
Setup is marginally cheaper (a sub-20-octet binary handshake against a few hundred octets of HTTP headers), and there is no header parsing per request. In steady state both are copying bytes between sockets, so throughput is dominated by the proxy's buffer handling and the network path, not by the protocol.
Does SOCKS5 encrypt traffic?#
No. SOCKS5 provides no confidentiality or integrity of its own, and RFC 1929 username/password credentials cross the wire in cleartext. Encryption must come from the payload (TLS) or from the transport carrying the SOCKS connection (an SSH dynamic forward, or a VPN).
Can a SOCKS5 proxy handle UDP?#
Yes, using the UDP ASSOCIATE command, which returns an address the client sends prefixed datagrams to. The association only lives as long as the TCP control connection that created it. Many deployed SOCKS servers disable UDP and answer with reply code 0x07.
Why does my hostname fail through a SOCKS proxy but the IP works?#
The client and the proxy are resolving in different places. If the client resolves locally, split-horizon or internal-only names return NXDOMAIN; if the proxy resolves, a local /etc/hosts override is ignored. Switch between socks5:// and socks5h:// to identify which side is failing.
Does ssh -D create a SOCKS5 proxy?#
Yes. OpenSSH's dynamic port forwarding implements a local SOCKS server that speaks SOCKS4 and SOCKS5 and forwards connections over the SSH session. It sends hostnames to the remote end for resolution, so DNS lookups happen at the SSH server rather than on the client.
Can I use SOCKS5 with the no_proxy environment variable?#
Sometimes, and inconsistently. curl applies NO_PROXY to ALL_PROXY including SOCKS schemes, whereas several language runtimes only consult bypass lists on the HTTP proxy path. Verify the behaviour for your specific client rather than assuming, since a bypass that silently does not apply sends internal traffic through the proxy.
Primary sources#
Every normative claim on this page is checked against the specification or the vendor documentation listed here. Where behaviour is version dependent, the version is named in the text.
Found something wrong, or behaviour that differs on your version? Report it with the version number and a primary source. Anything substantive is fixed in the page and logged on the corrections page. See editorial standards for how pages are researched, sourced and reviewed.