# SSL WRONG_VERSION_NUMBER con proxy: qué salto falló

Source: https://ipvolt.com/es/guides/fix-ssl-wrong-version-number-proxy
Markdown: https://ipvolt.com/es/guides/fix-ssl-wrong-version-number-proxy.md
Language: es

[Inicio de ipvolt](https://ipvolt.com/es.md) / [Guías](https://ipvolt.com/es/guides.md) / SSL WRONG_VERSION_NUMBER con proxy: qué salto falló

Solución de problemas
Publicado: 2026-10-04
8 min de lectura
Por ipvolt

El mismo error SSL wrong version number aparece en dos saltos detrás de un proxy. Cómo saber cuál falló en curl, Requests y HTTPX, y cómo corregir cada caso.

`wrong version number` es lo que informa OpenSSL cuando inicia un handshake TLS y los primeros bytes de la respuesta no son TLS. En la práctica son HTTP en texto plano: el cliente envió un ClientHello a un puerto que responde sin cifrar, y una respuesta como `HTTP/1.1 400 Bad Request` no puede leerse como un registro TLS. No interviene ningún certificado, versión de protocolo ni cifrado, así que `--tlsv1.2`, `--insecure` y `verify=False` no cambian nada.

Detrás de un proxy ese handshake puede ocurrir en dos lugares, y la mayoría de los clientes imprimen el mismo texto para ambos:

1. **Salto al proxy.** La URL del proxy dice `https://`, así que el cliente inicia TLS con el propio proxy, pero el puerto del proxy habla HTTP plano.
2. **Salto al destino.** La URL del proxy dice `http://`, el `CONNECT` tiene éxito y el cliente inicia TLS a través del túnel con un puerto de destino que no habla TLS, como `https://example.com:80/`.

| Cliente | Caso 1: salto al proxy | Caso 2: salto al destino |
| --- | --- | --- |
| curl 8.18.0 | `curl: (35) TLS connect error: error:0A00010B:SSL routines::wrong version number` | La misma línea |
| Requests 2.34.2 | `ProxyError`: `Unable to connect to proxy. Your proxy appears to only use HTTP and not HTTPS, try changing your proxy URL to be HTTP.` | `SSLError`: `[SSL: WRONG_VERSION_NUMBER] wrong version number`, sin pista |
| HTTPX 0.28.1 | `ConnectError: [SSL: WRONG_VERSION_NUMBER] wrong version number` | La misma línea |

Solo Requests nombra el salto. Con curl y HTTPX hace falta una comprobación más, descrita a continuación. Las filas de Python proceden de Python 3.12.3 con OpenSSL 3.0.13; las compilaciones más recientes de OpenSSL redactan el mismo fallo de otra forma, y la última tabla de esta página recoge todas las cadenas registradas.

## Cómo distinguir los dos saltos

**Lee primero la URL del proxy.** El caso 1 necesita una URL de proxy con `https://`. Imprime lo que el proceso usa de verdad, incluidas las variables que no definiste tú:

```sh
env | grep -i _proxy
```

Si todas las URL de proxy ya empiezan por `http://`, estás en el caso 2.

**curl: lee el estado de CONNECT.** `%{http_connect}` es el estado de la respuesta del proxy al `CONNECT`. Cero significa que curl no llegó tan lejos.

```sh
curl --silent --show-error --output /dev/null \
  --proxy "$PROXY_URL" \
  --write-out 'connect=%{http_connect} exit=%{exitcode}\n' \
  https://example.com/
```

- `connect=000 exit=35`: falló el handshake con el proxy. Caso 1.
- `connect=200 exit=35`: el túnel se abrió y falló el handshake con el destino. Caso 2.

`curl -v` muestra lo mismo. En el caso 2 estas líneas aparecen antes del error; en el caso 1 no hay ninguna línea `CONNECT`:

```text
> CONNECT 127.0.0.1:18000 HTTP/1.1
< HTTP/1.1 200 Connection established
* CONNECT tunnel established, response 200
* TLS connect error: error:0A00010B:SSL routines::wrong version number
```

La salida detallada también imprime la cabecera `Proxy-Authorization`, así que comparte la línea de write-out, no la traza.

**Requests: lee la clase de la excepción.** `requests.exceptions.ProxyError` significa que falló la conexión con el proxy. `requests.exceptions.SSLError` significa que TLS falló con el túnel ya abierto.

```python
import requests

proxy_url = "http://USERNAME:PASSWORD@proxy.example.net:8080"
proxies = {"http": proxy_url, "https": proxy_url}
try:
    requests.get("https://example.com/", proxies=proxies, timeout=(10, 20))
except requests.exceptions.ProxyError as error:
    print("proxy hop:", type(error).__name__)
except requests.exceptions.SSLError as error:
    print("target hop:", type(error).__name__)
```

La pista viene de urllib3, que solo la añade cuando el esquema de la URL del proxy es `https` y el error TLS contiene `wrong version number`, `unknown protocol` o `record layer failure`. Las versiones antiguas eran menos cuidadosas: urllib3 1.26.8 imprimía la pista también con URL de proxy `http://` ([issue #2564](https://github.com/urllib3/urllib3/issues/2564)). Con un urllib3 antiguo, fíate de la URL del proxy que imprimiste, no de la pista.

**HTTPX: cambia el esquema o lee el registro de httpcore.** HTTPX lanza el mismo `ConnectError` en ambos casos. Si la URL del proxy es `https://`, cámbiala a `http://` y vuelve a ejecutar: si el error desaparece era el caso 1, y si persiste el mismo error es el caso 2. Para una respuesta directa, activa el registro de depuración:

```python
import logging

logging.basicConfig(level=logging.DEBUG)
```

El caso 2 registra un `CONNECT` que devuelve 200 y después un fallo en `httpcore.proxy`:

```text
DEBUG:httpcore.http11:receive_response_headers.complete return_value=(b'HTTP/1.1', 200, b'Connection established', [])
DEBUG:httpcore.proxy:start_tls.failed exception=ConnectError(SSLError(1, '[SSL: RECORD_LAYER_FAILURE] record layer failure (_ssl.c:1081)'))
```

El caso 1 no registra ningún `CONNECT`, y la línea que falla es `httpcore.connection:start_tls.failed`. Este registro es de Python 3.14.4 con OpenSSL 3.5.5, por eso dice `RECORD_LAYER_FAILURE`.

## Corrección del caso 1: escribe http:// en la URL del proxy

El esquema de una URL de proxy describe cómo habla el cliente con el proxy, no lo que obtiene a través de él. Una URL de proxy `http://` transporta igualmente tráfico HTTPS: el cliente envía `CONNECT` en texto plano y después ejecuta TLS con el destino dentro del túnel. Usa `https://` solo cuando tu proveedor documente un puerto de proxy con TLS. La documentación de `--proxy` de curl trata la ausencia de esquema o `http://` como proxy HTTP y `https://` como proxy HTTPS.

```sh
curl --proxy http://USERNAME:PASSWORD@proxy.example.net:8080 https://example.com/
```

```python
import requests

proxy_url = "http://USERNAME:PASSWORD@proxy.example.net:8080"
response = requests.get(
    "https://example.com/",
    proxies={"http": proxy_url, "https": proxy_url},
    timeout=(10, 20),
)
```

```python
import httpx

with httpx.Client(proxy="http://USERNAME:PASSWORD@proxy.example.net:8080") as client:
    response = client.get("https://example.com/")
```

```sh
export HTTPS_PROXY=http://USERNAME:PASSWORD@proxy.example.net:8080
export HTTP_PROXY=http://USERNAME:PASSWORD@proxy.example.net:8080
```

En el laboratorio, el puerto de proxy que fallaba con `https://` devolvió 200 en los tres clientes en cuanto el esquema fue `http://`, definido en el código o mediante `HTTPS_PROXY`. Si el error sobrevive al cambio, puede que una variable de entorno siga aportando el valor antiguo: Requests y HTTPX leen `HTTPS_PROXY` de forma predeterminada (`trust_env`). [Variables de entorno de proxy](/es/guides/proxy-environment-variables) explica qué variable lee cada cliente, y las guías de [curl](/es/guides/curl-proxy-setup), [Requests](/es/guides/python-requests-proxy) y [HTTPX](/es/guides/httpx-async-proxy) contienen configuraciones completas.

## La trampa: la clave del diccionario no es el esquema del proxy

En `proxies={"https": ...}` la clave es el esquema de la URL que solicitas. El valor es la URL del proxy, con su propio esquema. No tienen por qué coincidir, y con un puerto de proxy HTTP plano no deben coincidir:

```python
# Case 1: TLS to a proxy port that speaks plain HTTP
proxies = {"https": "https://USERNAME:PASSWORD@proxy.example.net:8080"}

# Correct: HTTPS targets through an HTTP proxy
proxies = {"https": "http://USERNAME:PASSWORD@proxy.example.net:8080"}
```

Las variables de entorno funcionan igual. El nombre `HTTPS_PROXY` selecciona qué solicitudes usan el proxy, y el valor indica cómo llegar a él.

## Corrección del caso 2: corrige la URL de destino

El proxy está bien. La URL pide TLS en un puerto que sirve HTTP plano. Las causas habituales son `https://` combinado con el puerto 80 o con un puerto HTTP interno, y una URL base montada a partir de un esquema y un puerto que se configuran por separado. Confírmalo enviando HTTP plano por el mismo túnel:

```sh
curl --silent --show-error --proxy "$PROXY_URL" --proxytunnel \
  --write-out 'connect=%{http_connect} status=%{http_code}\n' \
  http://example.com:80/
```

En el laboratorio esto imprimió la página y `connect=200 status=200` para el puerto que había fallado con `https://`, mientras que un puerto que de verdad habla TLS dio `curl: (52) Empty reply from server`. Un estado HTTP aquí significa que el puerto habla HTTP plano: cambia la URL a `http://` o apúntala al puerto TLS. Si se te permite conectar sin el proxy, la misma URL falla igual de forma directa, lo que además descarta al proxy.

Si la URL es correcta y el error va y viene, los bytes que llegan por el túnel no proceden del servidor TLS de tu destino. El issue #2564 de urllib3 describe ese patrón con un proxy inestable. Anota la línea `connect=` con una marca de tiempo y envíasela al proveedor.

## Los reintentos no arreglan ninguno de los dos casos

Ambos casos son errores de configuración, y cada intento falla igual. El `Max retries exceeded` del mensaje de Requests no significa que se hayan hecho reintentos: el adaptador predeterminado está configurado con cero reintentos e imprime ese texto de todos modos. Una política de reintentos solo retrasa la misma excepción. Corrige la URL y reserva los reintentos para fallos que puedan cambiar entre intentos.

Dentro de un agente de programación compatible con MCP, el [Proxy Toolkit MCP](/mcp), que es gratuito, diagnostica errores de proxy por fase de la solicitud. Indícale el cliente, la excepción y el valor de `connect=`, sin credenciales.

## El texto depende de la compilación de OpenSSL

El texto procede de la biblioteca TLS, no del cliente, así que el mismo fallo se lee distinto según la compilación. Cada compilación imprimió la misma cadena en ambos saltos salvo que se indique lo contrario:

| Compilación | Texto del error |
| --- | --- |
| curl 8.18.0 (OpenSSL 3.5.5), curl 8.17.0 (OpenSSL 3.6.0), curl 8.22.0 (OpenSSL 4.0.2) | `curl: (35) TLS connect error: error:0A00010B:SSL routines::wrong version number` |
| curl 8.12.1 (OpenSSL 3.4.1) | `curl: (35) TLS connect error: error:0A0000C6:SSL routines::packet length too long` |
| curl 8.7.1 (OpenSSL 3.2.1) | `curl: (35) OpenSSL/3.2.1: error:0A0000C6:SSL routines::packet length too long` |
| Python 3.11.3 (OpenSSL 1.1.1s), Python 3.12.3 (OpenSSL 3.0.13) | `[SSL: WRONG_VERSION_NUMBER] wrong version number` |
| Python 3.14.4 (OpenSSL 3.5.5) | `[SSL: RECORD_LAYER_FAILURE] record layer failure` |
| Node.js 24.20.0 `fetch` (OpenSSL 3.5.7) | `TypeError: fetch failed`, código de causa `ERR_SSL_WRONG_VERSION_NUMBER` |
| Playwright 1.63.0 (Chromium 153) | Caso 1: `net::ERR_PROXY_CONNECTION_FAILED`. Caso 2: `net::ERR_SSL_PROTOCOL_ERROR` |

Así que `packet length too long` y `record layer failure` con un proxy de por medio son los mismos dos casos. En las tres compilaciones de Python, Requests lanzó `ProxyError` con la pista en el caso 1 y un `SSLError` sin más en el caso 2. `unknown protocol`, la tercera frase que busca urllib3, no apareció en ninguna compilación probada. Chromium es aquí el único cliente que separa los saltos por sí solo; [Corrige ERR_TUNNEL_CONNECTION_FAILED](/es/guides/fix-err-tunnel-connection-failed) cubre sus errores de proxy, y [Node.js fetch failed detrás de un proxy](/es/guides/fix-node-fetch-failed-proxy) explica cómo leer la cadena `cause`.

## Cómo se probó

El 4 de octubre de 2026, en un VPS con Ubuntu 26.04, con todo en `127.0.0.1`: un listener que responde a cualquier entrada con un `HTTP/1.1 400 Bad Request` en texto plano, un proxy `CONNECT` mínimo sobre HTTP plano, `python -m http.server` como destino HTTP plano y el mismo manejador detrás de un certificado desechable como destino TLS funcional. Sin cuenta de proxy y sin tráfico saliente. El caso 1 apuntó una URL de proxy `https://` al listener de texto plano y al proxy `CONNECT`. El caso 2 usó el proxy `CONNECT` con una URL `https://` para el puerto HTTP plano. Un control con la URL de proxy `http://` y el destino TLS devolvió 200 en todos los clientes.

Clientes: las compilaciones de curl anteriores; Requests 2.34.2 con urllib3 2.8.0 y HTTPX 0.28.1 con httpcore 1.0.9 en las tres compilaciones de Python; `fetch` de Node.js 24.20.0 con `NODE_USE_ENV_PROXY=1`; Playwright 1.63.0 con Chromium 153.0.8010.12. El [archivo del laboratorio](https://ipvolt.com/downloads/fix-ssl-wrong-version-number-proxy/wrong-version-number-lab.zip) contiene el [script del laboratorio](https://ipvolt.com/downloads/fix-ssl-wrong-version-number-proxy/lab.py), los scripts de cliente, el [README](https://ipvolt.com/downloads/fix-ssl-wrong-version-number-proxy/README.md) y los [resultados registrados](https://ipvolt.com/downloads/fix-ssl-wrong-version-number-proxy/results.json).

No se probó: macOS ni Windows, bibliotecas TLS distintas de OpenSSL y la propia de Chromium, proxies SOCKS, puertos de proxy TLS reales ni ninguna red de proxies comercial. Tampoco se probó un puerto de proxy que cierre la conexión o guarde silencio sin responder en texto plano; puede producir un error distinto.

## Guías relacionadas

- [Variables de entorno de proxy: HTTP_PROXY y NO_PROXY](/es/guides/proxy-environment-variables) explica qué variable leen curl, Requests y Node.
- [Usar un proxy con curl](/es/guides/curl-proxy-setup) cubre `-x`, el túnel `CONNECT` y los códigos de salida de curl.
- [Configurar Python Requests](/es/guides/python-requests-proxy) explica el diccionario `proxies` y `trust_env`.
- [Proxies asíncronos con HTTPX](/es/guides/httpx-async-proxy) cubre clientes explícitos y tiempos de espera del pool.
- [Corrige ERR_TUNNEL_CONNECTION_FAILED](/es/guides/fix-err-tunnel-connection-failed) cubre los rechazos del proxy en Playwright y Puppeteer.

ipvolt es un servicio de proxies para desarrolladores que todavía no está abierto; [únete a la lista de acceso anticipado](https://ipvolt.com/#waitlist-closing) y recibirás un solo correo cuando abra.

## Fuentes y lecturas adicionales

- [urllib3: Your proxy appears to only use HTTP and not HTTPS](https://urllib3.readthedocs.io/en/stable/advanced-usage.html#https-proxy-error-http-proxy)
- [urllib3 issue #2564: the HTTP-only proxy hint shown for http:// proxy URLs](https://github.com/urllib3/urllib3/issues/2564)
- [urllib3 2.8.0 source: _wrap_proxy_error in connection.py](https://github.com/urllib3/urllib3/blob/2.8.0/src/urllib3/connection.py)
- [curl: -x, --proxy and the proxy URL scheme](https://curl.se/docs/manpage.html#-x)
- [curl: --write-out variables including http_connect](https://curl.se/docs/manpage.html#-w)
- [Requests: proxies](https://requests.readthedocs.io/en/latest/user/advanced/#proxies)
- [HTTPX: proxies](https://www.python-httpx.org/advanced/proxies/)
- [HTTPX: logging](https://www.python-httpx.org/logging/)
- [RFC 9110: the CONNECT method](https://www.rfc-editor.org/rfc/rfc9110.html#name-connect)

## Guías relacionadas

- [Variables de entorno de proxy: HTTP_PROXY y NO_PROXY](https://ipvolt.com/es/guides/proxy-environment-variables.md)
- [Usar un proxy con curl: -x, variables de entorno, SOCKS5 y auth](https://ipvolt.com/es/guides/curl-proxy-setup.md)
- [Proxy en Python Requests: diccionario proxies, auth, SOCKS5](https://ipvolt.com/es/guides/python-requests-proxy.md)

## Sobre ipvolt

Los ejemplos usan configuraciones de proxy genéricas, con enlaces a la documentación técnica original. El comportamiento específico de cada producto debe comprobarse con tu proveedor. ipvolt sigue en desarrollo.

[Leer el original en inglés](https://ipvolt.com/guides/fix-ssl-wrong-version-number-proxy.md)

## Entérate cuando se abra el acceso.

ipvolt · En desarrollo

Estamos construyendo infraestructura de proxies para desarrolladores y equipos de datos. Únete a la lista de interés para recibir un aviso cuando ipvolt esté listo.

Un correo cuando se abra el acceso. Nada más.

[Solicitar acceso anticipado](https://ipvolt.com/es/guides/fix-ssl-wrong-version-number-proxy#waitlist-closing)

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

