# Почему ваш агент для программирования получает «CONNECT tunnel failed, response 403»

Source: https://ipvolt.com/ru/blog/connect-tunnel-failed-403
Markdown: https://ipvolt.com/ru/blog/connect-tunnel-failed-403.md
Language: ru

[ipvolt — главная](https://ipvolt.com/ru.md) / [Блог](https://ipvolt.com/ru/blog.md) / CONNECT tunnel failed, response 403: почему это ловят ИИ-агенты

Анализ
Опубликовано: 2026-10-02
Обновлено: 2026-10-02
Автор: ipvolt
7 мин чтения

curl вашего агента падает с CONNECT tunnel failed, response 403, хотя другие хосты работают. Прокси отказал в туннеле. Как понять, кто его заблокировал, и что исправить.

```text
curl: (56) CONNECT tunnel failed, response 403
```

Так curl 8.15–8.17 сообщает, что попросил прокси открыть туннель к хосту `https://` и получил отказ. curl 8.18.0 и 8.22.0 печатают то же сообщение, но с кодом выхода 7 вместо 56. Тот же отказ, записанный через один локальный тестовый прокси, в Python Requests 2.34.2 выглядит так — библиотека бросает `requests.exceptions.ProxyError` с сообщением:

```text
HTTPSConnectionPool(host='example.org', port=443): Max retries exceeded with url: / (Caused by ProxyError('Unable to connect to proxy', OSError('Tunnel connection failed: 403 Forbidden')))
```

А во встроенном `fetch` Node.js 24.20.0 с `NODE_USE_ENV_PROXY=1`, где каждый уровень `err.cause` напечатан отдельной строкой:

```text
depth 0: TypeError: fetch failed
depth 1: DOMException: Request was cancelled.
depth 2: RequestAbortedError [UND_ERR_ABORTED]: Proxy response (403) !== 200 when HTTP Tunneling
```

Во всех трёх случаях прокси сказал «нет» раньше, чем ваш запрос ушёл дальше. Через тот же прокси `https://example.com/` во всех клиентах вернул 200. Изменился только хост.

## Что означает этот 403

Для URL `https://` клиент отправляет прокси `CONNECT example.org:443` и ждёт разрешения открыть туннель. RFC 9110 говорит, что любой ответ 2xx переводит соединение в режим туннеля, а «любой ответ, кроме успешного, означает, что туннель ещё не образован». 403 — это отказ прокси открыть туннель. [RFC 9110](https://www.rfc-editor.org/rfc/rfc9110.html#name-connect)

Отсюда два следствия:

- **Дело не в учётных данных.** Прокси, которому нужны учётные данные, отвечает 407 с вызовом `Proxy-Authenticate`; этот случай разобран в [руководстве по 407](/ru/guides/fix-proxy-error-407). 403 означает, что прокси решил не пересылать этот запрос, поэтому смена пароля не поможет.
- **Целевой сайт так и не был достигнут.** Нет туннеля — нет TLS-рукопожатия и нет запроса к `example.org`. Это видно по write-out curl: `connect=403` — ответ прокси, а `http=000` означает, что сайт так ничего и не ответил.

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

Песочницы и CI-раннеры часто пропускают весь исходящий трафик через egress-прокси со списком разрешённых хостов. Адрес прокси приходит в `HTTPS_PROXY`, поэтому curl, pip, npm и `fetch` идут через него. Хосты из списка получают туннель; все остальные — 403. Поэтому из одной и той же оболочки один хост работает, а следующий падает.

Этот паттерн видно в двух публичных отчётах:

- В [anthropics/claude-code#56959](https://github.com/anthropics/claude-code/issues/56959) у Claude Code, работающего с Bedrock, `HTTPS_PROXY` указывал на собственный прокси песочницы на localhost. Этот прокси разрешал четыре хоста (`bedrock-runtime.us-east-1.amazonaws.com`, `api.anthropic.com` и два шаблона Sentry), а для любого другого хоста, по словам автора, «возвращает HTTP 403 от локального прокси». `curl https://github.com` падал с `curl: (56) CONNECT tunnel failed, response 403`. Автор сообщил, что записи `sandbox.network.allowedDomains` в пользовательских и управляемых настройках в этом режиме игнорировались. Issue закрыли как дубликат #37970.
- В [SocketDev/socket-sdk-js#659](https://github.com/SocketDev/socket-sdk-js/issues/659) агент, выполнявший еженедельное обновление зависимостей в CI, не смог завершить работу. Инструмент обновления taze искал версии на `npm.antfu.dev`, который «блокирует файрвол песочницы CI (403 CONNECT tunnel failed)». curl к этому хосту вернул HTTP 000 с ошибкой curl 56, а `curl https://registry.npmjs.org/semver` вернул 200. Мейнтейнеры исправили это, оставив запросы на `registry.npmjs.org`, который уже был в списке разрешённых.

## Как понять, кто сказал «нет»

Запустите падающий URL один раз с `curl -v` через тот же прокси. Строки, начинающиеся с `>`, — это то, что отправил curl; строки, начинающиеся с `<`, до `CONNECT tunnel failed` — ответ прокси. Вот curl 8.16.0 против тестового прокси:

```text
*   Trying 127.0.0.1:41745...
* CONNECT: no ALPN negotiated
* allocate connect buffer
* Establish HTTP proxy tunnel to example.org:443
> CONNECT example.org:443 HTTP/1.1
> Host: example.org:443
> User-Agent: curl/8.16.0
> Proxy-Connection: Keep-Alive
> 
< HTTP/1.1 403 Forbidden
< Content-Type: text/plain
< Content-Length: 32
< Connection: close
< 
* CONNECT tunnel failed, response 403
* closing connection #0
curl: (56) CONNECT tunnel failed, response 403
```

Строка `Trying` называет прокси, а не сайт. 403 приходит в ответ на `CONNECT`, и после него нет строк TLS, значит, сайт в обмене не участвовал. Если же 403 появляется после успешного `CONNECT` и TLS-рукопожатия, его прислал сайт, и прокси здесь ни при чём.

Затем сравните разрешённый и заблокированный хост через тот же прокси, используя write-out, чтобы разделить два кода состояния:

```sh
curl --disable -sS -o /dev/null -x http://127.0.0.1:41745 -w 'http=%{http_code} connect=%{http_connect} exit=%{exitcode}\n' https://example.com/
curl --disable -sS -o /dev/null -x http://127.0.0.1:41745 -w 'http=%{http_code} connect=%{http_connect} exit=%{exitcode}\n' https://example.org/
```

С curl 8.18.0 первая команда напечатала `http=200 connect=200 exit=0`. Вторая напечатала `http=000 connect=403 exit=7` и `curl: (7) CONNECT tunnel failed, response 403`. Тот же клиент, тот же прокси, другой хост: у прокси правило для каждого хоста. Повторите сравнение со своими разрешённым и заблокированным хостами. В песочнице агента возьмите хост, который точно работает, например реестр пакетов, до которого агент уже достучался.

Наконец, проверьте, какой прокси процесс использует на самом деле. Выведите `HTTPS_PROXY`, `HTTP_PROXY`, `NO_PROXY` и их варианты в нижнем регистре в той же оболочке или шаге задания, где возникает сбой. Хост из `NO_PROXY` идёт мимо прокси напрямую, а это песочница может блокировать иначе. [Руководство по переменным окружения прокси](/ru/guides/proxy-environment-variables) объясняет, как клиенты выбирают между этими переменными, а статья [NO_PROXY matching, tested](/ru/blog/no-proxy-matching-tested) показывает, что клиенты по-разному понимают, что совпадает с записью в `NO_PROXY`. Встроенный `fetch` Node игнорирует эти переменные, пока не задан `NODE_USE_ENV_PROXY=1` или `--use-env-proxy`, поэтому скрипт на Node и команда curl в одной оболочке могут идти разными маршрутами. [Документация Node.js CLI](https://nodejs.org/api/cli.html#node_use_env_proxy1)

## Исправьте, не обходя прокси

403 — это решение политики, поэтому исправлять нужно на стороне политики или на стороне источника:

- **Разрешите хост.** Добавьте точный домен, который нужен инструменту, в список разрешённых egress-хостов песочницы или CI. Берите домен из строки `CONNECT` в `curl -v` или из ошибки самого инструмента, а не из его конфигурации: в отчёте Socket taze искал версии на `npm.antfu.dev`, а не в реестре npm.
- **Используйте источник, который уже разрешён.** Зеркало, внутренний реестр или официальный реестр, который политика уже пропускает, часто избавляет от необходимости менять политику. Именно так исправили проблему в Socket.
- **Проверьте порт.** Некоторые прокси туннелируют только порт 443. Предлагаемая конфигурация Squid содержит `http_access deny CONNECT !SSL_ports`, где `SSL_ports` — порт 443. [Squid http_access](http://www.squid-cache.org/Doc/config/http_access/) В тестовом прокси, который разрешал `example.com` только на порту 443, `https://example.com:8443/` получил тот же `CONNECT tunnel failed, response 403`, что и заблокированный хост.
- **Проверьте правила провайдера для назначений.** Коммерческие прокси-провайдеры тоже могут отказывать в доступе к назначениям. Каталог ошибок Bright Data перечисляет ошибки политики в разделе HTTP 403, например «You tried to target %HOST% which is blocked by Bright Data policy». [Каталог ошибок Bright Data](https://docs.brightdata.com/proxy-networks/errorCatalog) Decodo перечисляет категории целей, например сайты банков и государственных органов, которые он по умолчанию ограничивает. [Ограниченные цели Decodo](https://help.decodo.com/docs/residential-proxy-restricted-targets)

Не обходите egress-правила песочницы. Если снять `HTTPS_PROXY`, добавить хост в `NO_PROXY` или пустить трафик через другой прокси, чтобы обойти список разрешённых хостов, вы сведёте на нет контроль, который кто-то поставил намеренно, — и это может просто не сработать: автор #56959 обнаружил, что песочница блокировала и прямой сетевой доступ в обход прокси. Попросите того, кто управляет песочницей или CI-раннером, разрешить хост, и передайте ему точную строку `CONNECT` из `curl -v`. Агент в отчёте Socket сделал именно так: остановился, сообщил о заблокированном хосте и не пытался обойти файрвол.

## Почему curl по-другому сообщает о http://

Для URL `http://` никакого `CONNECT` нет. curl отправляет прокси весь запрос, и 403 от прокси — это ответ на этот запрос. Тестовый прокси ответил 403 в обоих случаях, но curl сообщил о них по-разному:

| Через тот же прокси | curl 8.15.0–8.17.0 | curl 8.18.0 и 8.22.0 |
| --- | --- | --- |
| `https://example.org/` (заблокирован) | exit 56, `curl: (56) CONNECT tunnel failed, response 403` | exit 7, `curl: (7) CONNECT tunnel failed, response 403` |
| `http://example.org/` (заблокирован) | exit 0, страница 403 от прокси выведена как тело ответа | exit 0, страница 403 от прокси выведена как тело ответа |
| `http://example.org/` с `--fail` | exit 22, `curl: (22) The requested URL returned error: 403` | exit 22, `curl: (22) The requested URL returned error: 403` |
| `https://example.com/` (разрешён) | exit 0, `http=200 connect=200` | exit 0, `http=200 connect=200` |

Для `http://` `curl -v` показывает `> GET http://example.org/ HTTP/1.1`, затем `< HTTP/1.1 403 Forbidden`, и curl печатает тело. Мейнтейнер curl Даниэль Стенберг описал эту разницу в [обсуждении curl #15718](https://github.com/curl/curl/discussions/15718), которое посвящено 407, но относится к любому отказу: если туннель не удалось установить, передача не выполнена, и это ошибка, а GET к прокси, который вернул ответ, — это «*успешная* передача, содержащая код 4xx». Поэтому URL `http://` в скрипте без `--fail` может выглядеть как успех.

Другие клиенты разделились так же, с одним исключением. Python Requests вернул обычный `Response` со статусом 403 для `http://example.org/`. `fetch` в Node.js 24.20.0 с `NODE_USE_ENV_PROXY=1` открыл туннель `CONNECT example.org:80` даже для URL `http://`, поэтому упал с той же причиной `Proxy response (403) !== 200 when HTTP Tunneling`.

Ещё одна деталь версий: использованная нами статическая сборка curl 8.22.0 включает HTTP/3. Когда хост объявлял HTTP/3, она сначала пыталась открыть туннель `CONNECT-UDP`, поэтому `-v` показал два ответа 403 перед итоговым `curl: (7) CONNECT tunnel failed, response 403`.

## Воспроизведите у себя

Всё описанное выше записано 2 октября 2026 года с помощью лаборатории из [папки загрузок](/downloads/connect-tunnel-failed-403/README.md): прокси на Node.js на `127.0.0.1`, который туннелирует только к `example.com` на порт 443 и отвечает 403 на любой другой `CONNECT` и обычный HTTP-запрос; curl 8.15.0, 8.16.0, 8.17.0, 8.18.0 и 8.22.0, Python 3.14.4 с Requests 2.34.2 и urllib3 2.8.0 и Node.js 24.20.0. Сырой вывод — в [results.json](/downloads/connect-tunnel-failed-403/results.json). Тело ответа 403 тестового прокси (`403 Forbidden: lab proxy policy`) — его собственное; настоящий прокси песочницы или провайдера пришлёт другой текст.

## Связанные материалы

- [Как исправить ошибку прокси 407](/ru/guides/fix-proxy-error-407) — когда прокси вместо этого просит учётные данные.
- [Как исправить ERR_TUNNEL_CONNECTION_FAILED](/ru/guides/fix-err-tunnel-connection-failed) — тот же отказ внутри Chromium, Playwright и Puppeteer.
- [Переменные окружения прокси](/ru/guides/proxy-environment-variables) — какую переменную клиент читает на самом деле.
- [NO_PROXY matching, tested](/ru/blog/no-proxy-matching-tested) — почему запись для обхода совпадает в одном клиенте и не совпадает в другом.
- [Прокси и вычисления для AI-агента](/ru/blog/ai-agent-proxies-and-compute) — каким частям агента вообще нужен прокси.

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

## Источники

- [RFC 9110: CONNECT](https://www.rfc-editor.org/rfc/rfc9110.html#name-connect)
- [anthropics/claude-code#56959: sandbox allowlist answering 403 to CONNECT](https://github.com/anthropics/claude-code/issues/56959)
- [SocketDev/socket-sdk-js#659: CI sandbox firewall blocking one host](https://github.com/SocketDev/socket-sdk-js/issues/659)
- [curl discussion #15718: proxy responses for http and https URLs](https://github.com/curl/curl/discussions/15718)
- [Squid configuration: http_access suggested rules](http://www.squid-cache.org/Doc/config/http_access/)
- [Bright Data: proxy error catalog](https://docs.brightdata.com/proxy-networks/errorCatalog)
- [Decodo: residential proxy restricted targets](https://help.decodo.com/docs/residential-proxy-restricted-targets)
- [Node.js CLI: NODE_USE_ENV_PROXY](https://nodejs.org/api/cli.html#node_use_env_proxy1)

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

Пока вы здесь

ipvolt в разработке. Оставьте email, и мы один раз напишем, когда откроется доступ.

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

[Получить ранний доступ](https://ipvolt.com/ru/blog/connect-tunnel-failed-403#waitlist-blog-end)

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


## Похожие статьи

- [Парсинг японских цен: иены, полноширинные цифры и налоговые метки](https://ipvolt.com/ru/blog/japanese-price-parsing.md) (Анализ, 6 окт. 2026 г., 6 мин чтения): Разбирайте японские цены, не теряя валюту и признак налога. Запустите проверенную Python-фикстуру: полноширинная иена, смешанные цены и намеренные случаи «на проверку».
- [SOCKS5 и SOCKS5h: в чём разница и что отправляют 6 клиентов](https://ipvolt.com/ru/blog/socks5-vs-socks5h.md) (Сравнение, 4 окт. 2026 г., 7 мин чтения): socks5:// не везде означает локальный DNS. Мы записали, что curl, Requests, HTTPX, aiohttp, Playwright и Node отправляют SOCKS5-прокси для каждой схемы URL.
- [API коэффициентов или скрапинг: таблица расчёта затрат](https://ipvolt.com/ru/blog/betting-odds-api-vs-scraping.md) (Сравнение, 28 сент. 2026 г., 7 мин чтения): Сравните API коэффициентов и разрешённый скрапинг по охвату, свежести данных и смоделированной стоимости сбора с помощью редактируемой таблицы с явными допущениями.

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

- [Как исправить ошибку прокси 407, не гадая](https://ipvolt.com/ru/guides/fix-proxy-error-407.md): Разбираем ошибку Proxy Authentication Required по шагам: шлюз, метод аутентификации, кодирование учётных данных, область действия аккаунта и настройки клиента.
- [Как исправить ERR_TUNNEL_CONNECTION_FAILED в Playwright и Puppeteer](https://ipvolt.com/ru/guides/fix-err-tunnel-connection-failed.md): Разбираем ERR_TUNNEL_CONNECTION_FAILED и ERR_PROXY_CONNECTION_FAILED по проверенной матрице отказов прокси и показываем, как узнать статус CONNECT, скрытый Chromium.
- [Переменные окружения прокси: HTTP_PROXY и NO_PROXY](https://ipvolt.com/ru/guides/proxy-environment-variables.md): Разбираем различия в маршрутизации по HTTP_PROXY, HTTPS_PROXY, ALL_PROXY и NO_PROXY в curl, Python Requests и Node.js с помощью изолированной локальной проверки.

## О ipvolt

Технический анализ от команды ipvolt.

Доступ к ipvolt пока не открыт.

[Читать оригинал на английском](https://ipvolt.com/blog/connect-tunnel-failed-403.md)
