Fixing WRONG_VERSION_NUMBER when you use a proxy

Your proxy URL starts with https://, and the proxy speaks plain HTTP on that port. Changing one letter fixes it, in every language.

6 min readTested on requests 2.34 · Go 1.23 · Node 22 · curl 8.22
The client sends a TLS handshake to a proxy port that speaks plain HTTP; the proxy answers HTTP/1.1 400 and the client fails to read those bytes as a TLS record.https:// in the proxy URL, plain HTTP on the proxy portYour clientproxy = https://…Proxy, port 80speaks plain HTTPTLS ClientHelloHTTP/1.1 400 Bad RequestWhat the client receives where it expects a TLS record48H54T54T50P2F/3112E.311[SSL: WRONG_VERSION_NUMBER] wrong version numberReproduced with requests 2.34, Go 1.23, Node 22 and curl 8.22

In short

Change the proxy URL from https:// to http://. The scheme of a proxy URL says how to talk to the proxy, not to the website. HTTPS websites still work through an http:// proxy, and stay encrypted end to end.

https://proxy:9000 → http://proxy:9000

A proxy URL like http://USER:PASS@via.proxies.vision:9000 describes the connection from your program to the proxy. With http:// your client opens a plain TCP connection and sends an HTTP request. With https:// it starts a TLS handshake with the proxy first.

Almost every proxy, ours included, expects plain HTTP on its port. When your client opens with a TLS handshake, the proxy sees bytes that aren’t an HTTP request. Many proxies answer with HTTP/1.1 400 Bad Request. Your client is waiting for a TLS record, reads the letters HTTP where a version number should be, and reports a wrong version number.

The mistake is easy to make in Python, because requests takes a dictionary where both sides look like schemes. The key is the scheme of the website the entry applies to. The value is the proxy URL, with its own scheme.

SSLError{"https": "https://USER:PASS@via.proxies.vision:9000"}TLS to a plain-HTTP port
200{"https": "http://USER:PASS@via.proxies.vision:9000"}HTTPS site, plain HTTP to the proxy

The second line doesn’t weaken anything. For an https:// website your client sends the proxy a short CONNECT request, the proxy opens a tunnel, and the TLS handshake happens with the website through that tunnel. The proxy only forwards encrypted bytes.

What you see depends on your client and on how the proxy reacts to the unexpected handshake. We made the same mistake against two proxies: Squid, which answers in plain HTTP, and our own gateway, which closes the connection.

ClientProxy answers in plain HTTPProxy closes the connection
Python requests[SSL: WRONG_VERSION_NUMBER] wrong version number[SSL: UNEXPECTED_EOF_WHILE_READING] EOF occurred in violation of protocol
Python httpxConnectError: [SSL: WRONG_VERSION_NUMBER] wrong version numberConnectError: [SSL: UNEXPECTED_EOF_WHILE_READING] …
Go net/httpproxyconnect tcp: tls: first record does not look like a TLS handshakeproxyconnect tcp: EOF
Node, https-proxy-agentwrite EPROTO … wrong version numberProxy connection ended before receiving CONNECT response
Node, undiciERR_SSL_WRONG_VERSION_NUMBERECONNRESET: Client network socket disconnected before secure TLS connection was established
curl(35) TLS connect error: … wrong version number(35) TLS connect error: … unexpected eof while reading
Fig. 1 — The proxy URL set to https:// for a plain-HTTP proxy port, October 5, 2026. requests 2.34 (urllib3 2.8), httpx 0.28, Go 1.23, Node 22 with https-proxy-agent and undici, curl 8.22 (OpenSSL 3.5). requests, httpx and curl rechecked against port 9000 on October 14, 2026: the same errors.

A third kind of proxy does neither: tinyproxy kept waiting for an HTTP request, and requests ended with a read timeout after the full timeout. So a proxy request that hangs and then times out can be this same mistake.

In requests the message is wrapped in a ProxyError, and newer urllib3 versions add a hint: “Your proxy appears to only use HTTP and not HTTPS, try changing your proxy URL to be HTTP.” That hint is correct.

The fix in each client

Each of these ran against our gateway as shown, with real credentials in place of USER and PASS.

Python requests

fetch.py
import os

import requests

USER = os.environ["PROXY_USER"]
PASS = os.environ["PROXY_PASS"]

# The value is how to reach the PROXY: plain http://, also for https:// targets.
proxy = f"http://{USER}:{PASS}@via.proxies.vision:9000"
proxies = {"http": proxy, "https": proxy}

r = requests.get("https://ipinfo.io/ip", proxies=proxies, timeout=30)
print(r.status_code, r.text.strip())

httpx takes the same URL in one argument: httpx.Client(proxy="http://USER:PASS@via.proxies.vision:9000").

Go

go
proxyURL, _ := url.Parse("http://USER:PASS@via.proxies.vision:9000") // http, not https
client := &http.Client{
	Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)},
	Timeout:   20 * time.Second,
}
resp, err := client.Get("https://ipinfo.io/ip")

More on proxies in Go, including SOCKS5 and rotation: Proxy authentication in Go’s net/http.

Node.js

js
import axios from "axios";
import { HttpsProxyAgent } from "https-proxy-agent";

// "Https" in the class name is about the website. The proxy URL stays http://.
const agent = new HttpsProxyAgent("http://USER:PASS@via.proxies.vision:9000");
const res = await axios.get("https://ipinfo.io/ip", { proxy: false, httpsAgent: agent });

curl

bash
curl -x http://via.proxies.vision:9000 --proxy-user "USER:PASS" https://ipinfo.io/ip

An environment variable sets the proxy

requests, curl and many other tools read HTTPS_PROXY from the environment. If that variable holds an https:// proxy URL, you get this error from code that never mentions a proxy. Check with env | grep -i proxy. The name HTTPS_PROXY means “the proxy for https websites”; its value still starts with http://.

The website’s port doesn’t speak TLS

If the error is a plain SSLError without “Unable to connect to proxy” around it, the proxy worked and the far end is the problem. We got exactly that for an https:// URL pointing at a mail server’s port 25. Check the scheme and port of the URL you’re requesting.

A dropped exit

With residential proxies, a correct setup can still fail now and then with UNEXPECTED_EOF_WHILE_READING: the home connection behind that request went away during the handshake. Again there is no ProxyError around it, and the next request works. Retry those; here is a retry that knows the difference.

Is my traffic unencrypted if the proxy URL starts with http://?
No. For an https:// website your client asks the proxy to open a tunnel and then does the TLS handshake with the website through it. The proxy forwards encrypted bytes. Only the short request that opens the tunnel, with the hostname and your proxy login, travels to the proxy in plain HTTP.
Why do I get an EOF error instead of WRONG_VERSION_NUMBER?
It is the same mistake. It depends on what the proxy does when it receives a TLS handshake on its plain-HTTP port. If it answers with an HTTP error, your client reports a wrong version number. If it closes the connection, you get an unexpected EOF or a connection reset. If it waits, you get a timeout.
What does 'Your proxy appears to only use HTTP and not HTTPS' mean?
urllib3 adds that hint when it recognises this exact situation. It is telling you to change the proxy URL from https:// to http://.
Are there proxies that need https:// in the URL?
Yes, a few providers offer a TLS-encrypted connection to the proxy itself, usually on a separate port. Use https:// only when the provider's documentation says so for that port.
I get WRONG_VERSION_NUMBER without a proxy. Why?
Then the server you are calling doesn't speak TLS on that port, for example https://localhost:8000 for a development server that only speaks HTTP, or an https:// URL with port 80. Use http:// for that URL, or the port that has TLS.

Residential proxies, pay per GB

HTTP and SOCKS5. Country, city and sticky sessions in the username. No subscription.