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.
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.
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.
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.
| Client | Proxy answers in plain HTTP | Proxy closes the connection |
|---|---|---|
| Python requests | [SSL: WRONG_VERSION_NUMBER] wrong version number | [SSL: UNEXPECTED_EOF_WHILE_READING] EOF occurred in violation of protocol |
| Python httpx | ConnectError: [SSL: WRONG_VERSION_NUMBER] wrong version number | ConnectError: [SSL: UNEXPECTED_EOF_WHILE_READING] … |
| Go net/http | proxyconnect tcp: tls: first record does not look like a TLS handshake | proxyconnect tcp: EOF |
| Node, https-proxy-agent | write EPROTO … wrong version number | Proxy connection ended before receiving CONNECT response |
| Node, undici | ERR_SSL_WRONG_VERSION_NUMBER | ECONNRESET: 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 |
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
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
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
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
curl -x http://via.proxies.vision:9000 --proxy-user "USER:PASS" https://ipinfo.io/ipAn 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://?
Why do I get an EOF error instead of WRONG_VERSION_NUMBER?
What does 'Your proxy appears to only use HTTP and not HTTPS' mean?
Are there proxies that need https:// in the URL?
I get WRONG_VERSION_NUMBER without a proxy. Why?
Keep reading
aiohttp Proxy Errors: ClientHttpProxyError 407, SOCKS5 and trust_env
aiohttp's ClientHttpProxyError explained, why its message leaks your proxy password, socks5:// without aiohttp-socks, and rotation.
6 min read · #python#errors
httpx: unexpected keyword argument 'proxies' — Fix Without Downgrading
httpx 0.28 removed proxies=. What replaces it (proxy= and mounts=), the OpenAI SDK case, SOCKS5 and environment proxies.
5 min read · #python#errors
Axios HTTPS Proxy Not Working: httpsAgent, proxy: false and 407
Why an axios proxy silently doesn't apply, why HTTPS_PROXY beats your agent, and how to tell the proxy's 403 from the website's.
6 min read · #node.js#errors
Residential proxies, pay per GB
HTTP and SOCKS5. Country, city and sticky sessions in the username. No subscription.