curl через прокси: auth, DNS и NO_PROXY

11 мин чтения

Как направить curl через HTTP или SOCKS5: строчный http_proxy, CONNECT, socks5h, -U и NO_PROXY. Команды проверены на curl 8.7.1.

Текст для тех, кто запускает curl через HTTP или SOCKS5 и разбирает 407, DNS и «прокси задан, а запрос идёт напрямую». Мы не продаём IP и не выдаём ключи провайдера. Примеры ниже: плейсхолдеры USER:PASS и локальная фикстура на 127.0.0.1, не публичный выход сервиса.

01Как curl понимает схему прокси и схему цели

Проверено на curl 8.7.1 (libcurl/8.7.1, 27 марта 2024) против локального HTTP-форварда, CONNECT и SOCKS5 на 127.0.0.1, в изолированном окружении без унаследованного curlrc (curl -q) и без чужих *_proxy. Документация: everything curl, переменные окружения, HTTP-прокси и CONNECT, SOCKS, аутентификация, man curl, исходник curl 8.7.1 lib/url.c функция detect_proxy. Страницу libcurl-env читайте осторожно: там есть пример про http_proxy для https://, который расходится с этим исходником.

-x / --proxy задаёт прокси. URL в конце задаёт цель. Это разные схемы. HTTP-прокси для HTTPS-сайта обычно выглядит так:

curl -q -x http://127.0.0.1:8080 https://example.com/

Слева http:// это как говорить с прокси. Справа https:// это цель. curl не превращает прокси в «https-прокси» только потому, что цель HTTPS. По умолчанию хост в -x считается HTTP-прокси; порт без указания, 1080.

На фикстуре 14 октября 2026 команда

curl -q -sS -v --max-time 5 -x http://127.0.0.1:18081 http://127.0.0.1:18080/ip

подключилась к порту 18081, не к origin 18080, и отправила абсолютный URL:

* Connected to 127.0.0.1 (127.0.0.1) port 18081
> GET http://127.0.0.1:18080/ip HTTP/1.1
> Host: 127.0.0.1:18080

Тело origin вернуло JSON с "via":"http-origin" и пометкой, что это маршрутизация фикстуры, не exit провайдера. Прямой запрос без -x шёл на 18080 с обычным GET /ip.

Verbose удобен, но это не единственное доказательство. Журнал фикстуры или прокси (метод, цель, ATYP, 407) безопаснее, когда нельзя светить -v с учёткой. На боевом канале смотрите логи провайдера и фактический пир, не только вывод клиента.

Схема socks5:// и socks5h:// в том же -x меняет протокол до прокси, не тип IP. Датацентр, резидент и мобайл живут на другой оси. Протокол HTTP как фильтр каталога: HTTP-прокси. SOCKS5 как протокол: SOCKS5.

Порт HTTP и порт SOCKS у провайдера часто разные. -x http://127.0.0.1:18082 на SOCKS-порту фикстуры дал curl: (56) Recv failure: Connection reset by peer. Это не «хост выключен». Это чужой протокол на сокете. Сверьте в панели провайдера, какой порт подписан как HTTP CONNECT, какой как SOCKS5, и только потом копируйте строку в -x.

02Почему httpproxy пишут строчными

curl читает http_proxy, https_proxy/HTTPS_PROXY, ftp_proxy, ALL_PROXY/all_proxy и NO_PROXY/no_proxy. Исключение одно, его легко пропустить: http_proxy принимается только в нижнем регистре. Верхний HTTP_PROXY инструмент игнорирует. Причина в everything curl: CGI поднимает переменные HTTP_* из заголовков запроса. Заголовок Proxy: стал бы HTTP_PROXY и утащил бы скрипт на чужой прокси.

Имя переменной берётся из схемы цели, не из схемы прокси. Так устроена detect_proxy в curl 8.7.1 lib/url.c: из https собирается https_proxy, при отсутствии берётся HTTPS_PROXY, затем all_proxy/ALL_PROXY. Для HTTP цели нужен строчный http_proxy. Транспорт до прокси при этом может оставаться http://host:port. Задайте оба имени, если ходите и на http://, и на https://.

На macOS переменные чувствительны к регистру. Изолированный прогон curl 8.7.1 без curlrc:

env -i PATH=/usr/bin:/bin LC_ALL=C HTTP_PROXY=http://127.0.0.1:18081 \
  curl -q -sS -v --max-time 5 http://127.0.0.1:18080/ip

в verbose не написал про proxy env и подключился к origin:

* Connected to 127.0.0.1 (127.0.0.1) port 18080
> GET /ip HTTP/1.1

Строчный вариант с той же целью:

* Uses proxy env variable http_proxy == 'http://127.0.0.1:18081'
* Connected to 127.0.0.1 (127.0.0.1) port 18081

Для HTTPS цели строчный https_proxy на той же фикстуре взял прокси и открыл CONNECT:

* Uses proxy env variable https_proxy == 'http://127.0.0.1:18081'
> CONNECT 127.0.0.1:18083 HTTP/1.1

Верхний HTTPS_PROXY повёл себя так же. Конкурирующие порты подтверждают выбор по схеме цели: http_proxy=http://127.0.0.1:4 вместе с HTTPS_PROXY=http://127.0.0.1:3 для https://127.0.0.1/ дал Uses proxy env variable HTTPS_PROXY == 'http://127.0.0.1:3', connect на порт 3, код 7 (connection refused). Порт 4 из http_proxy не использовался.

Пример со страницы libcurl-env, где для https:// будто читается http_proxy, для curl 8.7.1 неверен. На Windows окружение часто регистронезависимое, поэтому HTTP_PROXY там может сработать. Вывод PowerShell на Linux CI без изолированной проверки не переносите.

-x перекрывает переменную. Пустой --proxy "" отключает прокси из env. Имеет смысл в CI, где кто-то уже экспортировал http_proxy на весь шелл. curl -q в начале строки отключает curlrc, иначе локальный конфиг тихо подмешает прокси.

03HTTPS и метод CONNECT

Для http:// цели HTTP-прокси обычно делает GET с абсолютным URL. Для https:// цели curl сначала говорит прокси CONNECT host:port, ждёт 200 Connection established, и только потом поднимает TLS внутри тоннеля. Прокси без CONNECT не откроет HTTPS. Это не баг клиента на телефоне и не повод менять семейство адресов.

Локально, origin на 18083 с самоподписанным сертификатом:

curl -q -4 --http1.1 -sS -v --max-time 8 -k -x http://127.0.0.1:18081 \
  https://127.0.0.1:18083/ip

фрагмент verbose:

* Establish HTTP proxy tunnel to 127.0.0.1:18083
> CONNECT 127.0.0.1:18083 HTTP/1.1
< HTTP/1.1 200 Connection established
* CONNECT tunnel established, response 200

Дальше TLS к origin, -k только потому, что сертификат фикстуры самоподписанный. На боевом сайте так не делайте: проверяйте CA провайдера или системное хранилище. Тело: "via":"https-origin".

Эта сессия доказала тоннель до локального HTTPS origin. Она не показывает публичный IP выхода. Чтобы увидеть адрес, который видит внешний контролируемый вами приёмник, сравните прямой запрос и запрос с рабочим -x к одной и той же цели, плюс логи пира или прокси. Страница мой IP и проверка прокси идут другой сетью, чем ваш curl. Зелёный чекер не доказывает, что именно этот процесс curl использовал тот же канал.

Если цифра на «мой IP» не сменилась, это повод проверить verbose, NO_PROXY, порт и логи. Это не доказательство обхода. Прямой канал и прокси могут выходить с одного публичного адреса, особенно в офисной сети. Локальная фикстура на 127.0.0.1 вообще не является публичным выходом.

HTTPS-прокси это уже TLS до прокси (https://proxy:443 в -x), не путайте с HTTPS-целью. По умолчанию порт такого прокси 443. Это шифрование канала до прокси, не замена типу IP.

04SOCKS5 и socks5h: кто резолвит имя цели

Буква h в socks5h:// относится к имени цели. curl отдаёт hostname прокси и не резолвит его локально. socks5:// резолвит имя цели у вас, на прокси уходит IP. То же самое: --socks5 против --socks5-hostname. Документация everything curl: SOCKS4 резолв у curl, 4a у прокси; SOCKS5 у curl, 5h у прокси. IPv6 через 4/4a нет, через 5/5h да.

Имя самого прокси (хост в -x) curl по-прежнему резолвит локально. socks5h://127.0.0.1 не отправляет DNS-запрос «как зовут прокси» на удалённый резолвер. Переносится только разрешение имени цели.

Числовой http://127.0.0.1:18080 на фикстуре дал только подписи режима:

# socks5://
* SOCKS5 connect to 127.0.0.1:18080 (locally resolved)

# socks5h://
* SOCKS5 connect to 127.0.0.1:18080 (remotely resolved)

Это не доказательство DNS-запроса и не доказательство отсутствия утечки. Нужна цель с hostname. Контрольный прогон с http://localhost:18080/ip и curl -q -4:

# socks5:// , локальный резолв
* Host localhost:18080 was resolved.
* SOCKS5 connect to 127.0.0.1:18080 (locally resolved)

# socks5h:// , имя ушло на прокси
* SOCKS5 connect to localhost:18080 (remotely resolved)

Журнал фикстуры (не verbose): для socks5 ATYP 1, name_kind=ipv4, host=127.0.0.1; для socks5h ATYP 3, name_kind=domain, host=localhost. Оба вернули fixture JSON. Без -4 socks5 сначала пытался ::1, origin слушает только IPv4, соединение закрылось. Это ограничение стенда, не «удалённый DNS сломан».

Эти записи показывают, какой ATYP ушёл на локальный SOCKS. Они не доказывают, что на ноутбуке не было параллельного системного DNS, и не доказывают поведение публичного резолвера провайдера. Для имён, которые живы только во внутреннем DNS, socks5h часто нужен по правилам сети. Для утечки DNS на стороне клиента socks5h или HTTP CONNECT с hostname в CONNECT ближе к цели, чем socks5 с локальным getaddrinfo.

Не вставляйте SOCKS-порт в -x http://. Не ждите, что смена socks5 на socks5h сменит ASN. Тип IP смотрите в карточке провайдера и в фильтрах резидент / мобайл, не в букве h. Удалённый DNS не маскирует хостинг и не включает чужой аккаунт.

05Логин к прокси без вывода пароля

HTTP и SOCKS могут требовать учётку. Неверный или пустой логин на HTTP даёт 407. Это ответ прокси, не сайта. Сайт со своей Basic-аутентификацией живёт отдельно (-u к origin, -U к прокси).

Без -U на прокси с Basic фикстура вернула:

< HTTP/1.1 407 Proxy Authentication Required
< Proxy-Authenticate: Basic realm="fixture"
http_code:407

Плейсхолдер в командной строке (только для стенда):

curl -q -sS --max-time 5 -x http://127.0.0.1:18084 -U 'USER:PASS' \
  http://127.0.0.1:18080/ip

вернул origin JSON без 407. На боевом канале так не делайте.

История шелла и список процессов это разные дыры. Строка -U USER:PASS попадает в .bash_history / .zsh_history, если её набрали в терминале. Подстановка -U "$PASS" всё равно раскрывает значение в argv в момент exec. man curl (-U, --proxy-user) прямо пишет: на системах, где это работает, curl затем затирает аргумент в process listing, но данные «всё ещё видны мгновение», и этого мало. Чувствительные данные нужно брать из файла или аналога, не из открытого argv.

Практичный минимум: не включайте -v, --trace и --trace-ascii с боевой учёткой (уйдёт Proxy-Authorization). Интерактивно вызовите curl -U USER и введите пароль с приглашения, если клиент его даёт. Либо положите proxy-user = USER:PASS в файл конфигурации curl с правами 0600 и укажите -K, --config. Тот же manpage разрешает --config - читать конфиг со stdin: пароль не обязан стоять в argv, если вы подаёте файл или поток, а не -U secret. Это не магия: кто читает stdin или файл 0600, тот видит секрет. Это уже не «переменная не попадает в ps».

.netrc это механизм логина к серверу (origin), не общий способ proxy auth. Для прокси опирайтесь на -U / --proxy-user или proxy-user в конфиге. Не коммитьте конфиг с паролем в репозиторий.

По умолчанию -U шлёт Basic. Если прокси просит Digest, NTLM или Negotiate, смотрите заголовок 407 и флаги --proxy-digest, --proxy-ntlm, --proxy-negotiate или --proxy-anyauth. Не лечите 407 сменой HTTP на SOCKS «на всякий случай». Сначала логин и схема auth. Если логин у провайдера длинный, со страной и сессией внутри имени пользователя, это всё ещё одна строка для proxy-user, а не новый протокол.

06NOPROXY и --noproxy

NO_PROXY это список через запятую: хосты, которые идут мимо прокси. Точка в начале имени матчит домен: .example.com захватит www.example.com, но не nonexample.com. * отключает прокси для всех. С curl 7.86.0 можно CIDR, например 192.168.0.0/16. Альтернатива в командной строке: --noproxy.

Фикстура, прокси в env, исключение origin, изолированный env:

* Uses proxy env variable NO_PROXY == '127.0.0.1,localhost'
* Connected to 127.0.0.1 (127.0.0.1) port 18080
> GET /ip HTTP/1.1

Запрос сел на origin, не на 18081. --noproxy 127.0.0.1 вместе с -x http://127.0.0.1:18081 вёл себя так же: connect на 18080, обычный GET /ip. Флаг перекрыл явный прокси для совпавшего хоста.

Типичный набор для локальной разработки:

export NO_PROXY=localhost,127.0.0.1,.internal.example,10.0.0.0/8

Без этого health-check на 127.0.0.1 уезжает в корпоративный прокси и падает таймаутом, хотя curl через прокси вы настраивали только для внешнего API. То же с внутренними именами вроде api.local: добавьте их в список явно. curl сам не угадает «это же локально».

Если прокси «не работает», сначала смотрите, не сидит ли цель в NO_PROXY, не экспортирован ли чужой http_proxy, не перепутан ли порт, не подмешал ли curlrc. Пошаговая диагностика без curl: прокси не работает.

07Как читать вывод локальной проверки

Что проверяетеКоманда-скелетЧто должно быть в verbose или в журналеЧто это не доказывает
HTTP forwardcurl -q -v -x http://HOST:PORT http://цельconnect к порту прокси, GET http://цель, либо запись форварда в логепубличный exit IP
HTTP_PROXY vs http_proxyизолированный env только одной переменнойверхний на Unix: connect к origin; строчный: Uses proxy env variable http_proxyповедение Windows
HTTPS CONNECTcurl -q -v -x http://HOST:PORT https://цельCONNECT цель:порт, tunnel established, либо CONNECT в логе проксигео, ASN, «сайт пустит»
SOCKS5 DNS целиhostname + -x socks5:// vs socks5h://ATYP ipv4/ipv6 против ATYP domain в логе проксиотсутствие системного DNS, тип IP
407-x без -U на auth-прокси407 Proxy Authentication Requiredчто SOCKS «лучше»
ОбходNO_PROXY или --noproxyconnect к origin, GET /pathчто прокси сломан
Чужой протоколHTTP-схема на SOCKS-портreset, curl 56что хост выключен

Локальная фикстура отвечает JSON "fixture":true. Это маршрутизация процесса на вашей машине. Реальный провайдер отвечает уже своим IP на внешней цели, которую вы контролируете. Сверьте пир и логи провайдера. Мой IP и чекер полезны как отдельные инструменты, не как доказательство того же сокета. Не публикуйте -v с Proxy-Authorization. Не утверждайте страну и доступ к аккаунту по факту CONNECT.

Семейство адресов это отдельный фильтр: IPv6. CONNECT через IPv4-прокси не делает цель IPv6.

08Что брать в каталоге под curl

curl говорит и HTTP CONNECT, и SOCKS5. Для скриптов чаще берут HTTP: меньше сюрпризов с -x http://. Если клиенту нужно, чтобы имя цели резолвил прокси, в URL пишите socks5h://. Дальше смотрите правила провайдера на протокол, авторизацию, разрешённые цели, лимит одновременных соединений и то, держит ли план стабильную сессию с cookie. Биллинг читайте в оферте выбранного плана, не в этом гайде. Нагрузку на чужой сервис гоняйте только в рамках договора и маленьким контрольным запросом.

Карточки сравнения на Top Proxy не выдают host, port и пароль. После выбора сервиса зайдите в свой аккаунт у провайдера, когда доступ уже выдан, и скопируйте строку подключения оттуда. В каталоге отфильтруйте заявленный протокол и откройте карточку только как сравнение, не как кабинет.

SOAX — резидентские и мобильные прокси
SOAX — резидентские и мобильные прокси3.9219

SOAX продаёт ротационные резидентские и мобильные прокси на общем пуле кредитов; в тех же планах доступны датацентровые и ISP. Builder — от $200/мес + VAT (200 кредитов); Tier 1 на Builder — $3/GB.

85 стран
3,00 $
за 1 GB
Bright Data — резидентские и датацентровые прокси
Bright Data — резидентские и датацентровые прокси4.41439

Bright Data — крупная сеть прокси для сбора данных: резидентские, мобильные, ISP и датацентровые IP примерно в 195 странах. Резидентский трафик можно оплачивать по факту без подписки, серверные и ISP-адреса продаются пакетами от 10 IP. Для резидентских и мобильных прокси нужна проверка личности.

10 стран
5,00 $
за 1 GB
Decodo (Smartproxy) — резидентские и датацентровые прокси
Decodo (Smartproxy) — резидентские и датацентровые прокси4.22106

Decodo (ранее Smartproxy) продаёт резидентские и датацентровые прокси. Резидентские: $4/GB без месячного плана, подписки от $3.75/GB за 3 GB. Оплата: карты, PayPal, Alipay, Google/Apple Pay, крипто (с оговорками).

83 страны
0,38 $
за 1 GB
Oxylabs — резидентские и датацентровые прокси
Oxylabs — резидентские и датацентровые прокси4.0770

Oxylabs продаёт резидентские, датацентровые и мобильные прокси. Резидентские планы от $6/GB (5 GB / мес). Оплата: карты, банковский перевод, AliPay, PayPal.

10 стран
0,59 $
за 1 GB
Froxy — резидентские, мобильные и серверные
Froxy — резидентские, мобильные и серверные4.1120

Froxy предлагает резидентские, мобильные, быстрые резидентские и серверные прокси с оплатой за трафик, месячными пакетами и платным тестом $1.99 / 3 дня / 100 МБ.

236 стран
5,50 $
за 1 GB
На сайт

После получения доступа у провайдера: -x с нужной схемой, без -v на боевой учётке, затем контролируемая цель и логи. Если 407, чините логин. Если маршрут подозрительно прямой, смотрите NO_PROXY, http_proxy/https_proxy, curlrc и журнал прокси. Не делайте вывод только по совпавшему IP на сторонней странице проверки.

Почему curl игнорирует HTTP_PROXY?

Для HTTP-цели curl читает только строчный http_proxy. Верхний HTTP_PROXY на Unix не используется, чтобы CGI не подхватил заголовок Proxy. Задайте http_proxy или флаг -x и проверьте изолированный verbose либо журнал прокси.

Чем socks5 отличается от socks5h в curl?

socks5 резолвит имя цели локально и отдаёт прокси IP. socks5h отправляет hostname цели прокси. Имя самого прокси в -x по-прежнему резолвится локально. Числовой 127.0.0.1 показывает только подпись режима, не DNS-запрос.

Зачем CONNECT, если прокси HTTP?

Для HTTPS-цели curl строит тоннель методом CONNECT, затем говорит TLS с сайтом внутри. Без CONNECT HTTPS через HTTP-прокси не откроется. Схема прокси при этом остаётся http://.

Что значит 407 в curl?

Прокси требует аутентификацию. Для стенда можно -U USER:PASS. Для боевой учётки лучше файл конфигурации с правами 0600 или --config со stdin, без -v. 407 это не ответ сайта.

Как исключить localhost из прокси?

NO_PROXY=localhost,127.0.0.1 или --noproxy с тем же списком. С curl 7.86.0 можно CIDR. --noproxy перекрывает даже явный -x для совпавшего хоста.

Доказывает ли локальный тест публичный IP?

Нет. Фикстура на 127.0.0.1 показывает маршрутизацию и флаги curl. Совпавший адрес на странице мой IP не доказывает обход. Смотрите логи пира или прокси и контролируемую цель.

Откуда брать логин и порт для curl?

Не из карточки сравнения на Top Proxy. Host, port и пароль выдаёт кабинет выбранного провайдера после того, как доступ уже открыт. В каталоге смотрите заявленный протокол и условия плана.