technology · PRACTICAL GUIDE
Python SSL WRONG_VERSION_NUMBER? Prove the proxy scheme and TLS endpoint first
Map the target and proxy schemes, inherited settings, CONNECT route and first responding endpoint before changing certificates, TLS versions or verification.

WRONG_VERSION_NUMBER usually means the bytes at a supposed TLS boundary were not a valid TLS record for that connection. In Python HTTP clients, a common cause is sending TLS to a proxy or port that actually speaks plaintext HTTP—but it is not the only cause.
Preserve the target URL, effective proxy mappings, environment and operating-system proxy discovery, CONNECT behavior and first responding endpoint. Then distinguish a route mismatch from certificate verification or TLS-version negotiation without using verify=False as a diagnostic shortcut.
Freeze the failing request and every proxy source
Record Python, Requests, urllib3 and OpenSSL versions, the target URL including scheme and port, and the complete exception chain. Preserve the PreparedRequest URL and method without logging secrets. Record whether the call uses a per-request proxies mapping, Session.proxies, HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, NO_PROXY or an operating-system proxy.
Do not change trust or TLS versions yet. Compare the same request with a clean environment and with the intended proxy policy, using a harmless endpoint and normal certificate verification. A direct success plus proxied failure isolates the route; failure on both paths keeps the destination or local TLS stack in scope.
Map the destination and proxy schemes separately
urllib3 documents four routes. An HTTP proxy can serve an HTTPS destination by accepting CONNECT and then carrying TLS to the destination. An HTTPS proxy means the client first creates TLS to the proxy; HTTPS-to-HTTPS then becomes TLS to the proxy, CONNECT, and another TLS session to the destination.
Therefore HTTPS_PROXY=http://proxy:port can be correct when that endpoint is a plaintext HTTP proxy. Changing only the variable name does not change the proxy protocol. Do not globally rewrite https:// to http:// until the proxy owner or a controlled probe confirms what the proxy port actually speaks.
Prove the first endpoint that receives TLS bytes
Resolve the proxy host and port from the effective configuration, not from memory. Capture whether CONNECT is sent and whether the first response resembles plaintext HTTP, a proxy authentication challenge or a TLS handshake. A local debugging proxy should be tested on a non-sensitive target and with credentials removed from logs.
Also test the destination port directly when policy allows. A URL such as https://host:port will fail if that port is an ordinary HTTP listener. The same surface error can appear when a load balancer, intercepting gateway or port mapping sends TLS to a plaintext service.
Keep certificate and protocol errors on separate branches
CERTIFICATE_VERIFY_FAILED means a peer presented a certificate chain or hostname that verification did not accept. WRONG_VERSION_NUMBER happens earlier at record or protocol interpretation and is not repaired by verify=False. Disabling verification can only remove authentication checks after a TLS session exists.
UNSUPPORTED_PROTOCOL or a deliberate minimum-version policy is another branch. Only after proving that the selected hop really speaks TLS should you compare supported TLS versions with the Python SSLContext and server policy. Do not lower system-wide TLS settings to fix a plaintext endpoint.
Repair one configuration source and prove the exit condition
Correct the source that actually supplied the wrong route: a per-request mapping, Session setting, process environment, service environment, operating-system proxy or deployment secret. Keep http:// for an HTTP proxy even when the destination key is https; use https:// only when the proxy endpoint itself supports TLS. Preserve NO_PROXY decisions separately.
Retest the original request with verification enabled. Confirm the expected proxy hop, successful CONNECT when required, the intended destination certificate and a normal response. Restart the real service or job once more to prove it did not succeed only because of a temporary clean shell, then retain the sanitized route record with the change.
