# Прокси в curl_cffi: авторизация, ошибка 407 и SOCKS

Source: https://ipvolt.com/ru/guides/curl-cffi-proxy-setup
Markdown: https://ipvolt.com/ru/guides/curl-cffi-proxy-setup.md
Language: ru

[ipvolt — главная](https://ipvolt.com/ru.md) / [Руководства](https://ipvolt.com/ru/guides.md) / Прокси в curl_cffi: авторизация, ошибка 407 и SOCKS

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

Настройка прокси в curl_cffi 0.16.3: учётные данные и impersonate, почему 407 на CONNECT даёт ConnectionError, а не ProxyError, правила HTTP_PROXY и socks5h.

Укажите учётные данные в URL прокси и передайте его в параметре `proxy`:

```python
from curl_cffi import requests

PROXY = "http://user:pass@proxy.example.com:8080"

with requests.Session(impersonate="chrome", proxy=PROXY) as session:
    response = session.get("https://example.com/")
    print(response.status_code)
```

Чтобы поймать сбои прокси, перехватывайте `ProxyError` и `ConnectionError` и проверяйте `status_code` на 407. В curl_cffi 0.16 прокси, который отклоняет `CONNECT`, вызывает `ConnectionError`, а не `ProxyError`.

В нашей лаборатории такой код прошёл авторизацию для запросов `https://` и `http://` на локальном прокси, который требует Basic-авторизацию. Всё, что описано ниже, записано 10 октября 2026 года с curl_cffi 0.16.3 (libcurl 8.21.0) на Python 3.14.4. `impersonate` выбирает профиль браузера, у которого curl_cffi берёт настройки TLS и HTTP/2. Все результаты ниже были одинаковыми с этим параметром и без него.

Три детали перед разбором ошибок:

- **Используйте `proxy` или `proxies`, но не оба сразу.** `proxies={"http": PROXY, "https": PROXY}` работал одинаково в отдельном запросе и в `Session`. Ключ — это схема запрашиваемого URL: когда в словаре был только ключ `"https"`, запрос `http://` ушёл напрямую. Передача обоих аргументов вызвала `TypeError: Cannot specify both 'proxy' and 'proxies'`.
- **Кодируйте пароль в URL процентным кодированием.** `p@ss:w/rd` сработал в виде `p%40ss%3Aw%2Frd`. Без кодирования запрос завершился `ProxyError` с текстом `curl: (5) Unsupported proxy syntax`. `proxy_auth=("user", "p@ss:w/rd")` вместе с URL прокси без учётных данных тоже сработал, и кодировать его не нужно.
- **Пишите `http://` для HTTP-прокси, в том числе для URL `https://`.** Схема описывает соединение с прокси. URL прокси со схемой `https://`, направленный на обычный HTTP-прокси лаборатории, вызвал `SSLError` с `curl: (35) TLS connect error` и `WRONG_VERSION_NUMBER`. Это сообщение разобрано в руководстве [SSL WRONG_VERSION_NUMBER через прокси](/ru/guides/fix-ssl-wrong-version-number-proxy).

## CONNECT tunnel failed, response 407: ConnectionError, а не ProxyError

```text
curl_cffi.requests.exceptions.ConnectionError: Failed to perform, curl: (7) CONNECT tunnel failed, response 407. See https://curl.se/libcurl/c/libcurl-errors.html first for more details.
```

Для URL `https://` curl_cffi сначала отправляет прокси запрос `CONNECT`. Прокси лаборатории ответил 407, и запрос завершился этой ошибкой. Сообщение было одинаковым без учётных данных и с неверным паролем.

- `isinstance(error, ProxyError)` возвращает `False`. `ProxyError` и `ConnectionError` — соседние классы, оба наследуют `RequestException`, поэтому `except ProxyError` эту ошибку не ловит.
- `error.code` равен 7.
- `error.response` — это `Response`, у которого `status_code` равен 0, а не 407. Статус есть только в тексте сообщения.
- Код 7 не означает именно 407. Когда прокси лаборатории не смог соединиться с целевым сервером, он ответил 502, и сообщение было `curl: (7) CONNECT tunnel failed, response 502`. Когда порт прокси никто не слушал, оно было таким: `curl: (7) Failed to connect to 127.0.0.1:8443 over proxy 127.0.0.1 after 0 ms: Could not connect to server`.

Класс исключения определяется кодом ошибки curl, а этот код менялся от выпуска к выпуску. Мы выполнили один и тот же запрос на двенадцати выпусках:

| curl_cffi | libcurl | Отклонённый `CONNECT` | Сбой SOCKS |
| --- | --- | --- | --- |
| 0.16.0–0.16.3 | 8.21.0 | `ConnectionError`, код curl 7 | `ProxyError`, код curl 97 |
| 0.7.4–0.15.0 | 8.7.0–8.15.0 | `ProxyError`, код curl 56 | `ProxyError`, код curl 97 |

До версии 0.15.0 включительно сообщение было `curl: (56) CONNECT tunnel failed, response 407`. Утилита curl перешла с кода выхода 56 на 7 в версии 8.18.0. Эта таблица и порядок проверки учётных данных есть в руководстве [Как исправить ошибку прокси 407](/ru/guides/fix-proxy-error-407).

Этот блок обрабатывает отклонённый `CONNECT` в версиях до и после изменения, сбой SOCKS и ответ 407:

```python
import re

from curl_cffi.requests.exceptions import ConnectionError, ProxyError

CONNECT_STATUS = re.compile(r"CONNECT tunnel failed, response (\d{3})")


class ProxyFailure(Exception):
    pass


def fetch(session, url):
    try:
        response = session.get(url)
    except ProxyError as error:
        raise ProxyFailure(f"proxy error, curl {error.code}") from error
    except ConnectionError as error:
        rejected = CONNECT_STATUS.search(str(error))
        if error.code == 7 and rejected:
            raise ProxyFailure(f"proxy answered CONNECT with {rejected[1]}") from error
        raise
    if response.status_code == 407:
        raise ProxyFailure("proxy answered 407")
    return response
```

В лаборатории он выдал `proxy answered CONNECT with 407`, `proxy answered CONNECT with 502`, `proxy answered 407` для URL `http://` и `proxy error, curl 97` для сбоя SOCKS. Для закрытого порта прокси он повторно выбросил `ConnectionError`.

## 407 для URL http://: ответ, а не исключение

Для URL `http://` туннеля нет. curl_cffi отправляет прокси полный URL в обычном запросе `GET`, так он работает начиная с 0.7.0b7 ([issue #326](https://github.com/lexiforest/curl_cffi/issues/326)), и ответ 407 от прокси возвращается как обычный ответ.

Исключения не было. `response.status_code` был равен 407, `response.ok` — `False`, а в заголовках был `Proxy-Authenticate: Basic realm="lab"`. Код, который только перехватывает исключения, примет это за успешный запрос с пустым телом.

`response.raise_for_status()` вызвал `curl_cffi.requests.exceptions.HTTPError: HTTP Error 407: Proxy Authentication Required`. У этого исключения `error.response.status_code` равен 407.

## Переменные окружения: http_proxy читается, HTTP_PROXY — нет

Когда прокси в коде не задан, libcurl берёт его из окружения:

- Для URL `http://` переменная `HTTP_PROXY` в верхнем регистре была проигнорирована, и запрос ушёл напрямую. С `http_proxy` в нижнем регистре он прошёл через прокси.
- Для URL `https://` использовались и `HTTPS_PROXY`, и `https_proxy`. С одной лишь `http_proxy` запрос `https://` ушёл напрямую.
- `ALL_PROXY` и `all_proxy` действовали для обеих схем.
- С `NO_PROXY=127.0.0.1` и с `no_proxy` в нижнем регистре запрос ушёл напрямую.

libcurl описывает это исключение в документации: переменные проверяются и в верхнем регистре, "except the http_proxy one which is only used lowercase" ([переменные окружения libcurl](https://curl.se/libcurl/c/libcurl-env.html)). Причина в CGI: сервер превращает заголовок запроса `Proxy:` в переменную `HTTP_PROXY` для скрипта ([Everything curl](https://everything.curl.dev/usingcurl/proxies/env.html)).

Scrapy читает `HTTP_PROXY` в верхнем регистре ([Прокси в Scrapy 2.19](/ru/guides/scrapy-proxy-setup)), а aiohttp не читает ни одной переменной, пока вы не передадите `trust_env=True` ([Прокси в aiohttp 3.14](/ru/guides/aiohttp-proxy-setup)). Другие клиенты сравниваются в руководстве [Переменные окружения прокси](/ru/guides/proxy-environment-variables).

Два результата легко упустить:

- **`NO_PROXY` имеет приоритет над аргументом `proxy`.** С переменной `NO_PROXY=127.0.0.1` запрос `requests.get(url, proxy=PROXY)` ушёл напрямую для обеих схем. Когда мы добавили `curl_options={CurlOpt.NOPROXY: ""}`, он снова прошёл через прокси. Над `http_proxy` аргумент имел приоритет.
- **`trust_env=False` не отключил переменные.** `Session(trust_env=False)` по-прежнему использовал `http_proxy` и `HTTPS_PROXY`. `proxy=""` тоже их не отключил.

Две настройки отправили запрос напрямую при заданных переменных:

```python
from curl_cffi import CurlOpt, requests

requests.get("https://example.com/", proxies={"all": ""})
requests.get("https://example.com/", curl_options={CurlOpt.NOPROXY: "*"})
```

Обе работают и как аргументы `Session`. Пустая строка прокси "explicitly disables the use of a proxy, even if there is an environment variable set for it" ([CURLOPT_PROXY](https://curl.se/libcurl/c/CURLOPT_PROXY.html)).

## SOCKS: socks5:// резолвит локально, socks5h:// отправляет имя хоста

Лаборатория запрашивала `http://dnsprobe.example:8080/` — имя, которое есть только в файле hosts как `127.0.0.2`, — через SOCKS5-сервер, который записывает каждое место назначения:

- С `socks5://127.0.0.1:1080` сервер записал `CONNECT atyp=IPv4 dest=127.0.0.2:8080`. curl_cffi сам разрешил имя.
- С `socks5h://127.0.0.1:1080` он записал `CONNECT atyp=DOMAIN dest=dnsprobe.example:8080`. Прокси получил имя хоста.

Для URL `https://` результаты были теми же. Это соглашение самого curl ([Прокси в curl](/ru/guides/curl-proxy-setup)). Клиенты, которые ему не следуют, перечислены в статье [socks5 и socks5h](/ru/blog/socks5-vs-socks5h).

Неудачное рукопожатие SOCKS — это тот случай, когда `ProxyError` действительно возникает:

```text
curl_cffi.requests.exceptions.ProxyError: Failed to perform, curl: (97) cannot complete SOCKS5 connection to 127.0.0.1. (1). See https://curl.se/libcurl/c/libcurl-errors.html first for more details.
```

`error.code` равен 97. Последнее число в скобках — код ответа, который прислал SOCKS-сервер: сервер лаборатории ответил 1, общий сбой. Когда порт целевого сервера был закрыт и сервер ответил 5, сообщение заканчивалось на `(5)`.

Схема, которая не соответствует порту, даёт другие ошибки:

- `socks5://`, направленный на порт HTTP-прокси, вызвал `ProxyError` с `curl: (97) Received invalid version in initial SOCKS5 response.`
- `http://`, направленный на порт SOCKS, вызвал `ProxyError` с `curl: (56) Proxy CONNECT aborted` для URL `https://`.
- Когда порт SOCKS никто не слушал, снова возник `ConnectionError` с кодом 7.

## AsyncSession: та же настройка, те же ошибки

```python
import asyncio

from curl_cffi import AsyncSession

PROXY = "http://user:pass@proxy.example.com:8080"


async def main():
    async with AsyncSession(impersonate="chrome", proxy=PROXY) as session:
        response = await session.get("https://example.com/")
        print(response.status_code)


asyncio.run(main())
```

Каждый асинхронный случай совпал со своим синхронным аналогом. Отклонённый `CONNECT` вызвал тот же `ConnectionError` с кодом 7. Для URL `http://` ответ 407 вернулся как обычный ответ. Сбой SOCKS вызвал `ProxyError` с кодом 97. `socks5://` и `socks5h://` отправили те же типы адресов, а `HTTP_PROXY` была проигнорирована, тогда как `http_proxy` использовалась.

## Повторы: retry повторяет запросы POST

По умолчанию curl_cffi запросы не повторяет. Лаборатория отправила `POST` через прокси на цель, которая читает запрос и закрывает соединение без ответа. Вызов завершился `ConnectionError` с `curl: (52) Empty reply from server`, а цель получила один `POST`.

С `requests.Session(proxy=PROXY, retry=1)` тот же вызов вернул 201, а цель получила тело дважды. `retry` повторил `POST` так же, как повторяет `GET`.

`retry=2` при отклонённом `CONNECT` отправил три запроса `CONNECT` и вызвал ту же ошибку, и с `Session`, и с `AsyncSession`. Повтор не исправляет неверные учётные данные.

Для запросов, которые небезопасно повторять, оставляйте `retry` равным 0 или отправляйте ключ идемпотентности. Что меняет такой ключ, показано в статье [Повторы через прокси: один потерянный ответ, две созданные задачи](/ru/blog/proxy-retries-duplicate-jobs). Для помощи с диагностикой внутри агента для написания кода [Proxy Toolkit MCP](/mcp) принимает очищенные описания ошибок. Не передавайте учётные данные в его аргументах.

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

На Ubuntu 26.04.1 с Python 3.14.4 и curl_cffi 0.16.3 (libcurl 8.21.0-IMPERSONATE с BoringSSL), всё на loopback-адресах: `python -m http.server` в роли HTTP- и HTTPS-целей, три прокси, которые требуют Basic-авторизацию и записывают каждый запрос, SOCKS5-сервер, который записывает каждое место назначения, и ещё один, который на каждый запрос отвечает кодом 1, а также цель, которая теряет ответ на первый `POST`. Имя `dnsprobe.example` существовало только в отдельной копии файла hosts. Каждый из 86 случаев выполнялся в новом интерпретаторе с чистым окружением. Для сравнения версий были установлены двенадцать выпусков от 0.7.4 до 0.16.3. Аккаунт прокси-сервиса не использовался.

[Архив лаборатории](https://ipvolt.com/downloads/curl-cffi-proxy-setup/curl-cffi-proxy-lab.zip) содержит [скрипт запуска](https://ipvolt.com/downloads/curl-cffi-proxy-setup/run_lab.py), [случаи](https://ipvolt.com/downloads/curl-cffi-proxy-setup/cases.py), [прокси](https://ipvolt.com/downloads/curl-cffi-proxy-setup/auth_proxy.py), [SOCKS5-сервер](https://ipvolt.com/downloads/curl-cffi-proxy-setup/socks5_server.py), [README](https://ipvolt.com/downloads/curl-cffi-proxy-setup/README.md), [записанные результаты](https://ipvolt.com/downloads/curl-cffi-proxy-setup/results.json) и [сравнение версий](https://ipvolt.com/downloads/curl-cffi-proxy-setup/versions.json).

Не проверялось: macOS и Windows, авторизация прокси по Digest и NTLM, SOCKS-прокси с авторизацией, прокси, до которых нужно подключаться по TLS, HTTP/3 и WebSocket.

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

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

- [curl_cffi documentation: Proxies](https://curl-cffi.readthedocs.io/en/latest/advanced.html#proxies)
- [curl_cffi API reference: Session (proxy, proxies, proxy_auth, trust_env, retry)](https://curl-cffi.readthedocs.io/en/latest/api.html#curl_cffi.requests.Session)
- [curl_cffi API reference: AsyncSession](https://curl-cffi.readthedocs.io/en/latest/api.html#curl_cffi.requests.AsyncSession)
- [curl_cffi API reference: Exceptions and warnings (ProxyError, ConnectionError)](https://curl-cffi.readthedocs.io/en/latest/api.html#exceptions-and-warnings)
- [curl_cffi documentation: Using CURLOPT_* in requests API (curl_options)](https://curl-cffi.readthedocs.io/en/latest/advanced.html#using-curlopt-in-requests-api)
- [curl_cffi issue #326: CONNECT was sent for http:// URLs, changed in 0.7.0b7](https://github.com/lexiforest/curl_cffi/issues/326)
- [libcurl: CURLOPT_PROXY (schemes, the empty string, socks5h)](https://curl.se/libcurl/c/CURLOPT_PROXY.html)
- [libcurl: CURLOPT_NOPROXY and the no_proxy variable](https://curl.se/libcurl/c/CURLOPT_NOPROXY.html)
- [libcurl environment variables: http_proxy is only used lowercase](https://curl.se/libcurl/c/libcurl-env.html)
- [Everything curl: proxy environment variables and the CGI reason](https://everything.curl.dev/usingcurl/proxies/env.html)
- [libcurl error codes: CURLE_COULDNT_CONNECT (7), CURLE_RECV_ERROR (56), CURLE_PROXY (97)](https://curl.se/libcurl/c/libcurl-errors.html)

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

- [Как исправить ошибку прокси 407, не гадая](https://ipvolt.com/ru/guides/fix-proxy-error-407.md)
- [Прокси в curl: флаг -x, переменные окружения, SOCKS5, аутентификация](https://ipvolt.com/ru/guides/curl-proxy-setup.md)
- [Переменные окружения прокси: HTTP_PROXY и NO_PROXY](https://ipvolt.com/ru/guides/proxy-environment-variables.md)

## О ipvolt

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

[Читать оригинал на английском](https://ipvolt.com/guides/curl-cffi-proxy-setup.md)

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

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

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

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

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

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

