# SSL WRONG_VERSION_NUMBER через прокси: где именно сбой

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

[ipvolt — главная](https://ipvolt.com/ru.md) / [Руководства](https://ipvolt.com/ru/guides.md) / SSL WRONG_VERSION_NUMBER через прокси: где именно сбой

Диагностика
Опубликовано: 2026-10-04
8 мин чтения
Автор: ipvolt

Ошибка SSL wrong version number через прокси бывает на двух участках: до прокси и до целевого порта. Как отличить их в curl, Requests и HTTPX и что исправить.

`wrong version number` — так OpenSSL сообщает, что начал рукопожатие TLS, а первые байты ответа оказались не TLS. На практике это обычный HTTP: клиент отправил ClientHello на порт, который отвечает открытым текстом, и ответ вроде `HTTP/1.1 400 Bad Request` невозможно прочитать как запись TLS. Сертификат, версия протокола и шифры здесь ни при чём, поэтому `--tlsv1.2`, `--insecure` и `verify=False` ничего не меняют.

За прокси такое рукопожатие может происходить в двух местах, и большинство клиентов печатают для обоих один и тот же текст:

1. **Участок до прокси.** В URL прокси указано `https://`, поэтому клиент начинает TLS с самим прокси, но порт прокси говорит на обычном HTTP.
2. **Участок до цели.** В URL прокси указано `http://`, `CONNECT` проходит успешно, и клиент начинает TLS через туннель с целевым портом, который не говорит на TLS, например `https://example.com:80/`.

| Клиент | Случай 1: участок до прокси | Случай 2: участок до цели |
| --- | --- | --- |
| curl 8.18.0 | `curl: (35) TLS connect error: error:0A00010B:SSL routines::wrong version number` | Та же строка |
| 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`, без подсказки |
| HTTPX 0.28.1 | `ConnectError: [SSL: WRONG_VERSION_NUMBER] wrong version number` | Та же строка |

Только Requests называет участок. Для curl и HTTPX нужна ещё одна проверка, она описана ниже. Строки Python получены на Python 3.12.3 с OpenSSL 3.0.13; более новые сборки OpenSSL формулируют тот же сбой иначе, и в последней таблице на этой странице собраны все записанные варианты.

## Как отличить один участок от другого

**Сначала прочитайте URL прокси.** Для случая 1 нужен URL прокси с `https://`. Выведите то, что процесс использует на самом деле, включая переменные, которые задавали не вы:

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

Если все URL прокси уже начинаются с `http://`, у вас случай 2.

**curl: смотрите статус CONNECT.** `%{http_connect}` — это статус ответа прокси на `CONNECT`. Ноль означает, что curl до этого шага не дошёл.

```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`: не удалось рукопожатие с прокси. Случай 1.
- `connect=200 exit=35`: туннель открылся, а рукопожатие с целью не удалось. Случай 2.

`curl -v` показывает то же самое. В случае 2 эти строки идут перед ошибкой; в случае 1 строки `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
```

Подробный вывод печатает и заголовок `Proxy-Authorization`, поэтому делитесь строкой write-out, а не трассировкой.

**Requests: смотрите класс исключения.** `requests.exceptions.ProxyError` означает, что не удалось соединение с прокси. `requests.exceptions.SSLError` означает, что TLS не удался уже после открытия туннеля.

```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__)
```

Подсказку добавляет urllib3, и только когда схема URL прокси — `https`, а ошибка TLS содержит `wrong version number`, `unknown protocol` или `record layer failure`. Старые версии были менее аккуратны: urllib3 1.26.8 печатал подсказку и для URL прокси с `http://` ([issue #2564](https://github.com/urllib3/urllib3/issues/2564)). На старом urllib3 доверяйте выведенному URL прокси, а не подсказке.

**HTTPX: смените схему или прочитайте журнал httpcore.** HTTPX в обоих случаях выбрасывает один и тот же `ConnectError`. Если URL прокси начинается с `https://`, замените его на `http://` и запустите снова: если ошибка исчезла, это был случай 1, а если осталась та же ошибка, это случай 2. Чтобы получить прямой ответ, включите отладочный журнал:

```python
import logging

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

В случае 2 в журнале виден `CONNECT` с ответом 200, а затем сбой в `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)'))
```

В случае 1 записи о `CONNECT` нет, а сбойная строка — `httpcore.connection:start_tls.failed`. Этот журнал снят на Python 3.14.4 с OpenSSL 3.5.5, поэтому в нём `RECORD_LAYER_FAILURE`.

## Исправление случая 1: http:// в URL прокси

Схема в URL прокси описывает, как клиент разговаривает с прокси, а не что он через него запрашивает. URL прокси с `http://` всё равно передаёт HTTPS-трафик: клиент отправляет `CONNECT` открытым текстом, а затем выполняет TLS с целью внутри туннеля. Используйте `https://`, только если провайдер документирует порт прокси с TLS. В документации curl к `--proxy` отсутствие схемы или `http://` означает HTTP-прокси, а `https://` — 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
```

В лаборатории порт прокси, который отказывал с `https://`, вернул 200 во всех трёх клиентах, как только схема стала `http://`, заданная в коде или через `HTTPS_PROXY`. Если ошибка осталась после замены, старое значение может по-прежнему приходить из переменной окружения: и Requests, и HTTPX по умолчанию читают `HTTPS_PROXY` (`trust_env`). В руководстве [Переменные окружения прокси](/ru/guides/proxy-environment-variables) разобрано, какую переменную читает каждый клиент, а полные настройки есть в руководствах по [curl](/ru/guides/curl-proxy-setup), [Requests](/ru/guides/python-requests-proxy) и [HTTPX](/ru/guides/httpx-async-proxy).

## Ловушка: ключ словаря — это не схема прокси

В `proxies={"https": ...}` ключ — это схема URL, который вы запрашиваете. Значение — это URL прокси со своей собственной схемой. Они не обязаны совпадать, а для порта прокси с обычным HTTP и не должны:

```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"}
```

С переменными окружения то же самое. Имя `HTTPS_PROXY` выбирает, какие запросы идут через прокси, а значение говорит, как до него добраться.

## Исправление случая 2: исправьте URL цели

С прокси всё в порядке. URL просит TLS на порту, который отдаёт обычный HTTP. Типичные причины: `https://` в сочетании с портом 80 или внутренним HTTP-портом, а также базовый URL, собранный из схемы и порта, которые настраиваются отдельно. Проверьте это, отправив обычный HTTP через тот же туннель:

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

В лаборатории для порта, который отказывал с `https://`, эта команда вывела страницу и `connect=200 status=200`, а порт, который действительно говорит на TLS, дал `curl: (52) Empty reply from server`. HTTP-статус здесь означает, что порт говорит на обычном HTTP: смените URL на `http://` или направьте его на порт с TLS. Если вам разрешено подключаться без прокси, тот же URL точно так же падает напрямую, и это заодно снимает подозрение с прокси.

Если URL верный, а ошибка то появляется, то исчезает, значит, байты, приходящие через туннель, идут не от TLS-сервера вашей цели. В issue #2564 urllib3 описана такая картина с нестабильным прокси. Запишите строку `connect=` с отметкой времени и отправьте её провайдеру.

## Повторные попытки не помогут ни в одном случае

Оба случая — ошибки конфигурации, и каждая попытка падает одинаково. `Max retries exceeded` в сообщении Requests не означает, что повторы выполнялись: адаптер по умолчанию настроен на ноль повторов и всё равно печатает этот текст. Политика повторов лишь откладывает то же исключение. Исправьте URL, а повторы оставьте для сбоев, которые могут измениться от попытки к попытке.

В совместимом с MCP агенте для программирования бесплатный [Proxy Toolkit MCP](/mcp) диагностирует ошибки прокси по фазе запроса. Сообщите ему клиент, исключение и значение `connect=`, без учётных данных.

## Формулировка зависит от сборки OpenSSL

Текст приходит из библиотеки TLS, а не из клиента, поэтому один и тот же сбой в разных сборках выглядит по-разному. Каждая сборка печатала одну и ту же строку на обоих участках, если не указано иное:

| Сборка | Текст ошибки |
| --- | --- |
| 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`, код причины `ERR_SSL_WRONG_VERSION_NUMBER` |
| Playwright 1.63.0 (Chromium 153) | Случай 1: `net::ERR_PROXY_CONNECTION_FAILED`. Случай 2: `net::ERR_SSL_PROTOCOL_ERROR` |

Значит, `packet length too long` и `record layer failure` при работе через прокси — это те же два случая. На всех трёх сборках Python Requests выбрасывал `ProxyError` с подсказкой в случае 1 и просто `SSLError` в случае 2. `unknown protocol`, третья фраза, которую ищет urllib3, не появилась ни на одной из протестированных сборок. Chromium здесь единственный клиент, который сам различает участки; его ошибки прокси разобраны в руководстве [Исправляем ERR_TUNNEL_CONNECTION_FAILED](/ru/guides/fix-err-tunnel-connection-failed), а чтение цепочки `cause` — в руководстве [Node.js fetch failed за прокси](/ru/guides/fix-node-fetch-failed-proxy).

## Как это тестировалось

4 октября 2026 года, на VPS с Ubuntu 26.04, всё на `127.0.0.1`: слушатель, который на любой ввод отвечает открытым текстом `HTTP/1.1 400 Bad Request`, минимальный `CONNECT`-прокси на обычном HTTP, `python -m http.server` как цель с обычным HTTP и тот же обработчик за одноразовым сертификатом как рабочая цель с TLS. Без аккаунта у прокси-провайдера и без исходящего трафика. В случае 1 URL прокси с `https://` указывал на слушатель с открытым текстом и на `CONNECT`-прокси. В случае 2 использовался `CONNECT`-прокси и URL с `https://` для порта с обычным HTTP. Контрольный прогон с URL прокси `http://` и целью с TLS вернул 200 во всех клиентах.

Клиенты: перечисленные выше сборки curl; Requests 2.34.2 с urllib3 2.8.0 и HTTPX 0.28.1 с httpcore 1.0.9 на трёх сборках Python; `fetch` в Node.js 24.20.0 с `NODE_USE_ENV_PROXY=1`; Playwright 1.63.0 с Chromium 153.0.8010.12. [Архив лаборатории](https://ipvolt.com/downloads/fix-ssl-wrong-version-number-proxy/wrong-version-number-lab.zip) содержит [скрипт лаборатории](https://ipvolt.com/downloads/fix-ssl-wrong-version-number-proxy/lab.py), клиентские скрипты, [README](https://ipvolt.com/downloads/fix-ssl-wrong-version-number-proxy/README.md) и [записанные результаты](https://ipvolt.com/downloads/fix-ssl-wrong-version-number-proxy/results.json).

Не тестировалось: macOS и Windows, библиотеки TLS, кроме OpenSSL и собственной библиотеки Chromium, SOCKS-прокси, настоящие порты прокси с TLS и коммерческие прокси-сети. Порт прокси, который закрывает соединение или молчит, не отвечая открытым текстом, тоже не тестировался и может дать другую ошибку.

## Похожие руководства

- [Переменные окружения прокси: HTTP_PROXY и NO_PROXY](/ru/guides/proxy-environment-variables) — какую переменную читают curl, Requests и Node.
- [Прокси в curl](/ru/guides/curl-proxy-setup) — `-x`, туннель `CONNECT` и коды выхода curl.
- [Прокси в Python Requests](/ru/guides/python-requests-proxy) — словарь `proxies` и `trust_env`.
- [Асинхронные прокси в HTTPX](/ru/guides/httpx-async-proxy) — явные клиенты и тайм-ауты пула.
- [Исправляем ERR_TUNNEL_CONNECTION_FAILED](/ru/guides/fix-err-tunnel-connection-failed) — отказы прокси в Playwright и Puppeteer.

ipvolt — прокси-сервис для разработчиков, который пока не открыт; [запишитесь в список раннего доступа](https://ipvolt.com/#waitlist-closing), и вы получите одно письмо, когда он откроется.

## Источники и дополнительное чтение

- [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)

## Связанные руководства

- [Переменные окружения прокси: HTTP_PROXY и NO_PROXY](https://ipvolt.com/ru/guides/proxy-environment-variables.md)
- [Прокси в curl: флаг -x, переменные окружения, SOCKS5, аутентификация](https://ipvolt.com/ru/guides/curl-proxy-setup.md)
- [Прокси в Python Requests: словарь proxies, аутентификация, SOCKS5](https://ipvolt.com/ru/guides/python-requests-proxy.md)

## О ipvolt

Примеры используют обобщённые настройки прокси со ссылками на оригинальную техническую документацию. Поведение конкретного продукта уточняйте у своего провайдера. ipvolt пока в разработке.

[Читать оригинал на английском](https://ipvolt.com/guides/fix-ssl-wrong-version-number-proxy.md)

## Узнайте, когда откроется доступ.

ipvolt · В разработке

Мы строим прокси-инфраструктуру для разработчиков и команд, работающих с данными. Оставьте email, чтобы получить уведомление, когда ipvolt будет готов.

Одно письмо, когда откроется доступ. Больше ничего.

[Получить ранний доступ](https://ipvolt.com/ru/guides/fix-ssl-wrong-version-number-proxy#waitlist-closing)

[Конфиденциальность](https://ipvolt.com/privacy)

