У NO_PROXY нет стандартной грамматики, и клиенты, которые её читают, толкуют большую её часть по-разному. В основной матрице лабораторного прогона на loopback 23 сентября 2026 года 15 клиентов получили одинаковые значения no_proxy и NO_PROXY: две сборки curl, wget, Go, пять клиентов на Python и шесть конфигураций Node.js. Из 49 сочетаний значения и запроса в основной матрице 21 маршрутизировалось одинаково во всех клиентах, а 28 — нет. Каждая ячейка есть в опубликованном results.csv: одна строка на клиент и запрос.
Если одно значение должно обслуживать их все, используйте только те формы, которые везде вели себя одинаково, и задайте обеим переменным одно и то же значение:
# example.com and 192.0.2.10 stand for your own domain and address
export no_proxy="localhost,127.0.0.1,example.com,.example.com,192.0.2.10"
export NO_PROXY="$no_proxy"- Пара
example.com,.example.com(проверялась какexample.test,.example.test) оказалась единственным написанием, которое покрыло домен и все его поддомены во всех 15 клиентах. Сnotexample.testона не совпала ни разу. - Точный IPv4-адрес во всех клиентах совпадал только сам с собой. Диапазон IPv4 CIDR сработал лишь в 4 из 15.
localhost,127.0.0.1во всех клиентах вывела127.0.0.1иlocalhostв обход прокси. Без записей для loopback прокси для loopback пропускал только Go.- wget читает только
no_proxy.net/httpв Go 1.27.1 предпочитаетNO_PROXY, когда заданы обе, а Go 1.28 должен вместо этого перейти наno_proxy. Держите значения двух имён одинаковыми.
Опубликованный набор данных проверяет эти записи в отдельных случаях. В разовом прогоне ровно той строки, что приведена выше, с example.test вместо example.com, каждый из её девяти тестовых запросов тоже маршрутизировался одинаково во всех 15 клиентах.
Проблемы создают два вида написаний. Одни ломают клиент или отключают список. IPv6-запись в квадратных скобках приводит к ошибке httpx и httpx2 при создании клиента; то же делает запись IPv6 CIDR в httpx 0.28.1. Пустая no_proxy в нижнем регистре рядом с заданной NO_PROXY отключила список в 9 клиентах. Другие написания часть клиентов молча игнорирует: *.example.com — 8 клиентов, * внутри списка — 9, диапазон IPv4 CIDR — 11, завершающую точку — 11, список через пробелы — 9, а запись host:port — от 4 до 6 в зависимости от её формы. Даже одиночную * игнорирует wget.
Эти результаты синтетические. Они получены на одном Mac с прокси на 127.0.0.1, зарезервированными именами в зоне .test и IP-адресами, выделенными для документации, и описывают, как зафиксированные версии клиентов выбирают маршрут, а не то, как ведёт себя какой-либо прокси-сервис. О том, как каждый клиент вообще читает переменные прокси, см. как клиенты читают HTTP_PROXY и NO_PROXY. Если fetch() в Node полностью игнорирует прокси, начните с исправления «TypeError: fetch failed» в Node.js за прокси.
Переносимое подмножество и написания, которых стоит избегать
В таблицах используются имена, которые запрашивал стенд: example.test, sub.example.test, a.b.example.test и notexample.test. Ни один клиент здесь не разрешает имя хоста, прежде чем принять решение: их исходный код сравнивает имена как текст, а curl, Go и Requests разбирают IP-адрес из URL для проверок CIDR. Ни один из 662 проксированных запросов не вызвал зафиксированного отказа песочницы на DNS-запрос или соединение, хотя журнал ядра часть строк с отказами отбрасывает. Поэтому реальные доменные имена должны вести себя так же, как эти.
15 клиентов, каждый из которых читал переменные прокси из окружения с указанными настройками:
- curl 8.7.1 (macOS) и curl 8.22.0;
- GNU Wget 1.25.0;
- Go 1.27.1,
net/httpсhttp.ProxyFromEnvironment; - Python 3.14.7:
urllib, requests 2.34.2, httpx 0.28.1, httpx2 2.13.1 и aiohttp 3.14.3 сtrust_env=True; - встроенные
fetch()иhttp.get()в Node.js 26.10.0 и 24.21.0, каждый сNODE_USE_ENV_PROXY=1(ниже они называются «Node 26 fetch», «Node 26 http» и так далее); fetch()из npm-пакетов undici 7.29.1 и 8.11.0 сdispatcher: new EnvHttpProxyAgent(), оба на Node 26.10.0.
Эти записи дали одинаковый результат во всех 15 клиентах для указанных запросов:
| Запись | Результат во всех клиентах |
|---|---|
example.test | Обход для example.test; notexample.test по-прежнему шёл через прокси (поддомены различались в клиенте http Node) |
.example.test | Обход для sub.example.test и a.b.example.test (корневой домен различался в 4 клиентах) |
example.test,.example.test | Обход для корневого домена и обоих поддоменов; notexample.test по-прежнему шёл через прокси |
192.0.2.10 | Обход для этого адреса; 192.0.2.11 по-прежнему шёл через прокси |
EXAMPLE.TEST или запрос к EXAMPLE.TEST | Регистр букв ни на что не влиял |
other.test , example.test | Пробел после запятой ни на что не влиял |
other.test,,example.test или other.test,, | Пустые элементы ни с чем не совпадали |
localhost,127.0.0.1,::1 | Обход для 127.0.0.1 и localhost ([::1] различался; см. раздел о loopback) |
no_proxy задана, NO_PROXY не задана | Все клиенты читали переменную в нижнем регистре |
У каждого написания, которого стоит избегать согласно ответу выше, ниже есть свой раздел с перечнем разошедшихся клиентов.
Wildcard в NO_PROXY: совпадает ли *.example.com с example.com?
Зависит от клиента, и 8 из 15 вообще не восприняли её как подстановочный шаблон. С NO_PROXY=*.example.test:
| Запрос | Обошли прокси | Использовали прокси |
|---|---|---|
example.test | Node 26 fetch, Node 24 fetch, undici 7.29.1 | Остальные 12 |
sub.example.test, a.b.example.test | Go, fetch и http в Node 26 и 24, undici 7.29.1 и 8.11.0 | curl (обе сборки), wget, urllib, requests, httpx, httpx2, aiohttp |
notexample.test | Никто | Все 15 |
curl, wget и все пять клиентов на Python с этой записью не совпали ни с чем. Go, клиент http в Node и undici 8.11.0 прочитали её как «только поддомены», а встроенный fetch() в Node и undici 7.29.1 обошли прокси и для корневого домена.
Расхождение по корневому домену между встроенным fetch() в Node 26 и npm-пакетом undici 8.11.0 появилось недавно. undici PR #5777, вышедший в undici 8.11.0 22 сентября 2026 года, говорит, что запись *.domain «должна совпадать только с sub.example.com / a.b.example.com, но не с корневым доменом». Node 26.10.0 по-прежнему поставляется с undici 8.10.2. Поэтому в одной и той же среде Node 26.10.0 встроенный fetch() обошёл прокси для example.test, а npm-пакет undici 8.11.0 отправил запрос на прокси. Тот же pull request заставил * работать внутри списка и с пробелами вокруг, чем и объясняются единственные остальные строки, где эти два клиента разошлись.
Чтобы поддомены совпадали во всех клиентах, пишите .example.com. Добавьте example.com, если корневой домен тоже должен идти в обход. Ни одно проверенное написание не оставляло корневой домен на прокси во всех 15 клиентах, одновременно выводя его поддомены в обход.
Ведущая точка и домен без неё: .example.com, example.com и поддомены
| Запись и запрос | Обошли прокси | Использовали прокси |
|---|---|---|
example.test, запрос sub.example.test | 13 клиентов | Node 26 http, Node 24 http |
.example.test, запрос example.test | 11 клиентов | wget, Go, httpx, httpx2 |
Домен без точки совпадал со своими поддоменами везде, кроме встроенного клиента http в Node, который сопоставлял example.test точно. Стенд воспроизвёл node#65616, впервые описанную на Node 24.18.1, на 26.10.0 и 24.21.0. Собственный список форматов NO_PROXY в документации Node называет example.com «Exact host name match» (точное совпадение имени хоста), что описывает http.request(), а не fetch(). Исправление, node#65617, на 23 сентября 2026 года всё ещё было открыто.
Ведущая точка совпадала с каждым поддоменом во всех клиентах, но wget, Go, httpx и httpx2 не применяли её к корневому домену. Go это документирует: доменное имя с ведущей точкой «совпадает только с поддоменами» (httpproxy).
Ни одна из двух записей ни в одном клиенте не совпала с notexample.test. Requests соблюдает эту границу доменной метки только с версии 2.34.0; её примечания к выпуску говорят, что она «больше не выполняет жадное сопоставление доменов no_proxy». Более старые версии Requests не проверялись.
NO_PROXY=* и * внутри списка
| Значение | Обошли прокси | Использовали прокси |
|---|---|---|
* | 14 клиентов | wget |
other.test,* или " * " | Go, httpx, httpx2, Node 26 http, Node 24 http, undici 8.11.0 | curl (обе сборки), wget, urllib, requests, aiohttp, Node 26 fetch, Node 24 fetch, undici 7.29.1 |
* работает как всё значение целиком, без пробелов, во всех клиентах, кроме wget. curl так это и документирует: «единственный доступный подстановочный знак — одиночный символ *» (CURLOPT_NOPROXY). В wget 1.25.0 подстановочных знаков нет. Чтобы одна команда wget не пошла через прокси, используйте её опцию --no-proxy (руководство wget).
CIDR, диапазоны IP и подсети в NO_PROXY
С NO_PROXY=192.0.2.0/24 запрос к 192.0.2.10 обошёл прокси в curl 8.7.1, curl 8.22.0, Go и requests. Остальные 11 отправили его на прокси: wget, urllib, httpx, httpx2, aiohttp и все шесть клиентов Node. 198.51.100.10, вне диапазона, шёл через прокси во всех клиентах. Ни один клиент не выдал ошибку на IPv4-диапазон; 11 клиентов без его поддержки просто его проигнорировали.
- curl добавил поддержку CIDR в 7.86.0 (changelog).
- Requests проверяет CIDR в собственной функции сопоставления, прежде чем передать решение urllib, — поэтому два клиента на Python и расходятся.
- httpx и httpx2 приняли запись, но диапазон не сопоставляли. httpx2 PR #1165, который добавил бы диапазоны CIDR, на 23 сентября был открыт.
- Список форматов NO_PROXY в документации Node вместо CIDR описывает диапазон через дефис вида
192.168.1.1-192.168.1.100. Стенд диапазоны через дефис не проверял. Комментарий в node#57872 сообщает, что на предварительной сборке v27 диапазон через дефис работал вnode:http, но не вfetch().
Чтобы в смешанном стеке подсеть не ходила через прокси:
- Перечислите точные адреса, к которым обращаются ваши клиенты. Точные IPv4-адреса были единственной формой, совпавшей во всех 15.
- Рядом с ними можно добавить IPv4-диапазон для curl, Go и Requests. С
192.0.2.10,192.0.2.0/24разовый прогон вне опубликованного набора данных обошёл прокси для192.0.2.10во всех 15 клиентах, а для192.0.2.11— только в тех 4; ни один клиент не выдал ошибку. Диапазоны IPv6 не включайте: httpx 0.28.1 на них падает (см. ниже). - Диапазон применяется только к IP-адресу, записанному в URL. curl, Go и Requests не разрешают имя хоста заранее, поэтому
192.0.2.0/24не покрывает имя, которое в него разрешается. Это следует из их исходного кода; стенд использовал только IP-адреса.
IPv6 в NO_PROXY: без скобок, в скобках и CIDR
Для запроса к http://[2001:db8::10]/:
| Запись | Обошли прокси | Не обошли |
|---|---|---|
2001:db8::10 | 12 клиентов | urllib, Node 24 fetch, undici 7.29.1 использовали прокси |
[2001:db8::10] | urllib, Node 26 fetch, Node 24 fetch, undici 7.29.1, undici 8.11.0 | 8 использовали прокси; httpx и httpx2 выдали ошибку |
2001:db8::/48 | curl 8.22.0, Go | 12 использовали прокси; httpx выдал ошибку |
[2001:db8::10]:8080, запрос на порт 8080 | Go, urllib, Node 26 fetch, Node 24 fetch, undici 7.29.1, undici 8.11.0 | 7 использовали прокси; httpx и httpx2 выдали ошибку |
Форма без скобок показала себя лучше всего: она совпала в 12 клиентах и не сломала ни одного. curl именно её и требует: «указывайте числовые IPv6-адреса в списке имён хостов без квадратных скобок» (CURLOPT_NOPROXY). undici добавил сопоставление IPv6 без скобок в 8.10.0 (PR #5623); в undici 7.29.1, который поставляется с Node 24.21.0, его нет.
Из двух сборок curl IPv6-диапазон сопоставила только 8.22.0. До 8.18.0 (curl#19828) функция сопоставления curl считала хост IPv6-адресом, только если он приходил в квадратных скобках, чего никогда не бывает, поэтому запись IPv6 CIDR совпасть не могла.
InvalidURL: Invalid port в httpx из-за IPv6-записи в квадратных скобках
В httpx 0.28.1 и httpx2 2.13.1 запись в квадратных скобках хуже, чем проигнорированная. Создание клиента с обработкой окружения по умолчанию выбрасывало InvalidURL: Invalid port: 'db8::10]' ещё до отправки какого-либо запроса. С NO_PROXY=localhost,127.0.0.1,[::1] ошибкой была Invalid port: ':1]', и запросы к 127.0.0.1 и localhost тоже не выполнялись, потому что клиент так и не был создан. httpx 0.28.1 так же падал на 2001:db8::/48 (Invalid port: 'db8::'). httpx2 2.5.0 это падение устранил («Allow IPv6 CIDR notation in no_proxy», changelog), но, как показывает таблица, диапазон он по-прежнему не сопоставляет.
Чтобы это исправить, записывайте IPv6-записи без скобок, например ::1 или 2001:db8::10; с ними оба клиента создавались нормально. Не включайте IPv6-диапазоны ни в одно значение, которое читает httpx 0.28.1. Если изменить переменную нельзя, создавайте этот клиент с trust_env=False — так он тоже создавался нормально — и передавайте ему прокси в коде, как это делает руководство по прокси в HTTPX. Тогда этот клиент игнорирует все переменные прокси.
Порты в NO_PROXY: host:port
Для запроса на указанный в записи порт 8080:
| Запись | Обошли прокси | Использовали прокси |
|---|---|---|
example.test:8080 | 11 клиентов | curl (обе сборки), wget, aiohttp |
192.0.2.10:8080 | 10 клиентов | curl (обе сборки), wget, requests, aiohttp |
.example.test:8080 | 9 клиентов | curl (обе сборки), wget, aiohttp, Node 26 http, Node 24 http |
На порту 80 все три записи во всех клиентах вели через прокси, то есть ни один клиент не применил запись с портом к другому порту. Сбой был обратным: часть клиентов вообще не сопоставляла такую запись. Документация curl не описывает синтаксис портов в записях, а wget сравнивает только имя хоста. aiohttp передаёт функции сопоставления urllib хост без порта (исходный код). Requests ни разу не сопоставил IPv4-запись с портом: с 192.0.2.10:8080 он использовал прокси как на порту 8080, так и на порту 80. Его проверка IPv4 сравнивает только хост, согласно PR #7586 — исправлению, которое на 23 сентября всё ещё было открыто.
Не включайте порты в общее значение. Если один порт должен обходить прокси, настройте этот обход в том клиенте, которому он нужен.
Пробелы, пустые элементы, завершающие точки и верхний регистр
- Пробел перед запятой: с
example.test ,other.testwget оставил завершающий пробел частью записи и отправилexample.testна прокси. Остальные 14 обошли прокси. - Пробелы вместо запятых: с
other.test example.testcurl 8.7.1, Node 26 fetch, Node 24 fetch, undici 7.29.1 и undici 8.11.0 обошли прокси для обоих хостов. curl 8.22.0 обошёл его только дляother.test. Остальные 9 не обошли ни для одного. Изменение внёс curl 8.9.0: «noproxy: patterns need to be comma separated» (шаблоны должны разделяться запятыми; changelog). - Пустые элементы:
other.test,,ни в одном клиенте не вывелаexample.testв обход прокси, то есть ни один клиент не трактовал пустой элемент как «совпадает со всем». - Завершающие точки: запись
example.test.или запрос кhttp://example.test./совпадали только в curl 8.7.1, curl 8.22.0, Node 26 fetch и undici 8.11.0. undici добавил это в 8.10.1 (PR #5637), поэтому в Node 24 fetch и undici 7.29.1 этого нет. - Верхний регистр: записи и хосты заглавными буквами совпадали во всех 15 клиентах.
NO_PROXY или no_proxy: что побеждает, и ловушка пустой переменной
Для запроса к example.test:
| Окружение | Обошли прокси | Использовали прокси |
|---|---|---|
Только NO_PROXY=example.test | 14 клиентов | wget |
Только no_proxy=example.test | Все 15 | Никто |
NO_PROXY=example.test, no_proxy=other.test | Go 1.27.1 | Остальные 14 |
NO_PROXY=example.test, no_proxy="" | curl (обе сборки), Go, requests, Node 26 http, Node 24 http | wget, urllib, httpx, httpx2, aiohttp, Node 26 fetch, Node 24 fetch, undici 7.29.1, undici 8.11.0 |
Когда два имени расходятся, net/http в Go 1.27.1 использует NO_PROXY, а остальные 14 — no_proxy. Пустая переменная в нижнем регистре делит клиентов в соотношении 6 к 9. curl, Go, requests и клиент http в Node считают её незаданной и переходят к NO_PROXY. Остальные девять считают её пустым списком. node#66202, открытая 22 сентября 2026 года по предварительной сборке, описывает это расхождение между fetch() и http.request(). Стенд воспроизвёл его на выпущенных Node 26.10.0 и 24.21.0.
Приоритет в Go меняется. Коммит a02ddfa7ea в golang.org/x/net, сделанный для golang/go#79656 и выпущенный в x/net v0.58.0, заставляет пакет httpproxy предпочитать имена в нижнем регистре, а черновик примечаний к выпуску Go 1.28 перечисляет то же изменение для ProxyFromEnvironment. В отдельной проверке x/net v0.58.0 и v0.59.0 в конфликтном случае уже следовали no_proxy. Пустые значения Go по-прежнему пропускает, поэтому его результаты для пустой переменной не меняются.
У переменной прокси та же ловушка. С http_proxy="" и заданной HTTP_PROXY прокси использовали только Go, Node 26 http и Node 24 http; остальные 12 пошли напрямую. curl читает http_proxy только в нижнем регистре.
Задавайте обоим именам одно и то же значение, а переменную удаляйте через unset, а не экспортируйте пустой.
localhost, 127.0.0.1 и ::1: только Go пропускает прокси сам по себе
Вообще без NO_PROXY Go пошёл напрямую для 127.0.0.1, localhost и [::1]. Его пакет httpproxy документирует, что localhost и loopback-адреса никогда не используют прокси. Остальные 14 клиентов отправили все три на прокси. Поэтому с настроенным удалённым прокси эти клиенты отправят запрос к локальному серверу разработки на этот прокси, если loopback не указан в списке.
| Значение | 127.0.0.1 и localhost | [::1] |
|---|---|---|
localhost,127.0.0.1,::1 | Обход во всех 15 (Go: встроенное правило для loopback) | Обход в 12 (Go: встроенное правило для loopback); urllib, Node 24 fetch и undici 7.29.1 использовали прокси |
localhost,127.0.0.1,[::1] | httpx и httpx2 выдали ошибку; 13 обошли прокси (Go: встроенное правило для loopback) | Обход в urllib, Node 26 fetch, Node 24 fetch, undici 7.29.1 и 8.11.0, а в Go — по его встроенному правилу для loopback; httpx и httpx2 выдали ошибку; 7 использовали прокси |
Обходы Go в этой таблице объясняются его встроенным правилом, а не списком, поэтому они не показывают, что [::1] работает в Go как запись; в основной матрице Go использовал прокси для записи в скобках [2001:db8::10].
Укажите localhost,127.0.0.1. Добавьте ::1 без скобок, если используете IPv6 loopback, и никогда не пишите [::1].
Получает ли HTTPS через CONNECT то же решение об обходе?
В этом прогоне — да. Для каждого из 8 HTTPS-URL все клиенты сделали тот же выбор, что и для соответствующего HTTP-URL: 120 из 120 сравнений. Каждый проксированный HTTPS-запрос приходил на прокси как CONNECT.
Node.js: NO_PROXY не работает с fetch или http.request
Четыре разные причины могут создать впечатление, что NO_PROXY в Node сломан. Случай с явным ProxyAgent взят из документации undici и не проверялся; остальные три — результаты стенда.
Явное включение. Без NODE_USE_ENV_PROXY=1 или --use-env-proxy (документация) fetch() и http.get() в Node 26 и 24 игнорировали переменные прокси и шли напрямую во всех 36 контрольных ячейках. Этот сбой разбирает руководство по fetch в Node.
Явный ProxyAgent. Документация undici описывает ProxyAgent как агент, который направляет каждый запрос через свой прокси, и не упоминает никакого списка обхода; чтение no_proxy и NO_PROXY добавляет именно EnvHttpProxyAgent (документация undici 8.11.0 для ProxyAgent и EnvHttpProxyAgent). Поэтому код, который передаёт ProxyAgent как dispatcher, как это делает руководство по настройке прокси для fetch в Node.js, игнорирует NO_PROXY. Чтобы сохранить список обхода, используйте new EnvHttpProxyAgent(), который читает переменные или принимает опции httpProxy, httpsProxy и noProxy.
Два механизма сопоставления в одной среде выполнения. http.request() использует собственный механизм сопоставления Node, а встроенный fetch() — EnvHttpProxyAgent из undici. Они разошлись в 20 из 72 небазовых строк на Node 26.10.0 и в 19 из 72 на Node 24.21.0. В коде сопоставления Node стоит комментарий TODO(joyeecheung): share code with undici. (исходный код), и общий код по-прежнему остаётся открытым пунктом в сводной задаче node#57872.
Встроенный undici и undici из npm. На Node 26.10.0 встроенный fetch() (undici 8.10.2) и npm-пакет undici 8.11.0 разошлись в 4 строках — все они случаи с wildcard. fetch() в Node 24.21.0 и npm-пакет undici 7.29.1 не разошлись ни в одной. undici 7.29.1 и 8.11.0 разошлись в 11 строках.
Таблица ниже показывает по одной строке на каждый вид расхождения на Node 26.10.0 со значениями из разделов выше. Остальные 12 из 20 разошедшихся строк повторяют те же виды для других хостов и запросов, плюс случай http_proxy="".
| Различие | fetch | http | undici 8.11.0 |
|---|---|---|---|
| Домен без точки, запрос к поддомену | Обход | Прокси | Обход |
*.example.test, запрос к корневому домену | Обход | Прокси | Прокси |
* внутри списка | Прокси | Обход | Обход |
| IPv6-адрес в квадратных скобках | Обход | Прокси | Обход |
| Ведущая точка с портом | Обход | Прокси | Обход |
| Запись с завершающей точкой | Обход | Прокси | Обход |
| Пробелы вместо запятых | Обход | Прокси | Обход |
Пустая no_proxy, задана NO_PROXY | Прокси | Обход | Прокси |
Node 24.21.0 показал ту же картину с двумя исключениями. Для завершающей точки оба его клиента использовали прокси. Для IPv6-записи без скобок обход выполнил только http.
Комментарий от 2026-09-01 в node#57872 уже сравнивал node:http с fetch() на предварительной сборке v27.0.0. Строки стенда по Node согласуются со всеми 8 строками той таблицы, которые он смог сравнить.
Что GitLab выяснил в 2021 году и что изменилось
Статья Stan Hu 2021 года в блоге GitLab, с исследованием Nourdin el Bacha, сравнила curl, wget, Ruby, Python, Go и Java по их исходному коду и документации. Она рекомендовала наименьший общий знаменатель и предложила стандарт.
Из 32 ячеек её таблицы no_proxy, которые этот стенд смог проверить (8 строк для curl, wget, Python через urllib и Go), 31 по-прежнему совпадает. Исключение — curl и CIDR: таблица 2021 года говорит «нет», но здесь обе сборки curl его учли. Ячейка приоритета «Uppercase» для Go всё ещё соответствует Go 1.27.1 и должна измениться в Go 1.28. Стенд не смог проверить строки «Supports regexes?» и «Resolves IP addresses?»; вторая помечает Go как «Yes», но сам текст статьи говорит, что имена хостов разрешает только Ruby, а исходный код Go 1.27.1 этого не делает. Ruby и Java не проверялись.
Её совет о наименьшем общем знаменателе выдерживает проверку лишь отчасти. no_proxy только в нижнем регистре, запятые и точные IPv4-адреса сработали во всех 15 клиентах, ведущая точка по-прежнему не покрыла корневой домен в 4, как статья и предупреждала, а IPv4 CIDR учли только 4, так что избегать CIDR по-прежнему разумно. «Suffixes are always matched» (суффиксы совпадают всегда) не подтвердилось в клиенте http Node, «comma-separated hostname:port values» (значения hostname:port через запятую) совпадали лишь в 9–11 клиентах на запись (8 совпали по всем трём), а ни одна форма IPv6 не дала единого результата.
Некоторые из этих особенностей появились недавно. Requests 2.34.0 (11 мая 2026 года) добавил границу доменной метки. httpx2 2.5.0 (25 июня 2026 года) перестал падать на записях IPv6 CIDR, не сопоставляя их. undici 8.10.0, 8.10.1 и 8.11.0 (август и сентябрь 2026 года) добавили IPv6 без скобок, завершающие точки и новые правила для wildcard.
Как устроен тест и как его повторить
Небольшой HTTP-прокси на 127.0.0.1 записывал в журнал каждый запрос в абсолютной форме и каждый CONNECT, отвечал на него сам и ничего не пересылал. Каждый запрос выполнялся в собственном процессе под macOS sandbox-exec с профилем, который запрещает весь исходящий трафик, кроме loopback, поэтому прямая попытка завершалась ошибкой на самой машине и не достигала ни одного реального хоста. Ячейка считается проксированной, только когда прокси записал её запрос; получил ли клиент ответ, ни на что не влияло. Без заданной NO_PROXY все 15 клиентов проксировали все формы URL.
Опубликованный прогон 23 сентября 2026 года с 19:13 до 19:15 UTC содержит 1 266 ячеек. Чистый повторный прогон по шагам ниже из опубликованного архива классифицировал все 1 266 ячеек одинаково.
Чтобы прочитать опубликованную матрицу, ничего не запуская, распакуйте архив и выполните python3 no_proxy_lab.py table results.json. Этой команде не нужны ни подготовка, ни сеть, и на Python 3.9.6 она напечатала тот же вывод, что и на 3.14.7. Полный повторный прогон требует macOS, Python 3.14 и Go, около 700 МБ на диске и доступа к nodejs.org, pypi.org и registry.npmjs.org во время setup, который ничего не устанавливает в систему:
curl -fLO https://ipvolt.com/downloads/no-proxy-matching-tested/no-proxy-matching-tested.zip
unzip no-proxy-matching-tested.zip
cd no-proxy-matching-tested
python3.14 no_proxy_lab.py setup --work ./work
python3.14 no_proxy_lab.py run --work ./work --out ./out
python3.14 no_proxy_lab.py compare results.json out/results.json
python3.14 no_proxy_lab.py table out/results.json --section maincompare завершается с кодом 0, только когда у каждой ячейки одинаковая классификация. README объясняет его вывод при других сборках клиентов, как проверить другие версии или собственное значение NO_PROXY, режим --isolation none для других систем и каждое поле results.json.
Загрузки:
- no-proxy-matching-tested.zip: все файлы ниже с каталогом
clients/на месте (воссоздайте его, если скачиваете файлы по одному) - README.md: метод, требования, команды и как читать результаты
- results.json и results.csv: каждая ячейка опубликованного прогона
- no_proxy_lab.py, clients/py_client.py, clients/node_client.cjs, clients/go_client.go и nonet.sb: обвязка, клиенты на один запрос и профиль песочницы только для loopback
- requirements.txt, package.json и package-lock.json: точные версии зависимостей Python и npm
Ограничения
- Одна машина с macOS 15.7.4 arm64, одна дата и версии, указанные выше. Любой выпуск может изменить строку: Node 26.10.0 пока не поставляется с undici 8.11.0, а Go 1.28 должен изменить строку с конфликтующими переменными.
- Linux не проверялся. Каждый клиент сопоставляет NO_PROXY в собственном коде или в стандартной библиотеке своего языка, а при заданных переменных прокси urllib в Python идёт по той же ветке для окружения на macOS, что и на Linux. Поэтому на Linux строки должны зависеть от версии клиента, а не от операционной системы. Это вывод из исходного кода, а не результат на Linux; примеры приведены в README.
- Матрица проверяла только обработку переменных окружения с перечисленными выше настройками и только HTTP forward-проксирование. Она не проверяла явные опции прокси, другие агенты или диспетчеры, системные настройки прокси, Windows, SOCKS, PAC-файлы, редиректы и аутентификацию на прокси. Об ответе 407 см. как исправить ошибку прокси 407.
- Диапазоны через дефис, частичные адреса вроде
192.168.*, записи, зависящие от разрешения DNS, а также Ruby, Java, .NET, Deno и Bun не проверялись; остальное перечислено в README. - Две разовые проверки, упомянутые выше, выполнены 24 сентября 2026 года с той же обвязкой и теми же клиентами. Их нет в
results.json; README показывает, как их повторить.
ipvolt выполнил эти проверки локально 23 и 24 сентября 2026 года с перечисленными выше версиями клиентов. Ни внешний провайдер, ни прокси-сервис, ни сеть не проверялись, и ничто здесь не описывает собственный сервис ipvolt.
Если хотите узнать, когда откроется доступ к ipvolt, запишитесь в список раннего доступа. Одно письмо, когда откроется доступ. Больше ничего.
Источники
- GitLab (2021): We need to talk: Can we standardize NO_PROXY?
- nodejs/node#57872: node:http vs fetch() NO_PROXY table (comment, 2026-09-01)
- nodejs/node#65616: NO_PROXY=example.com and http.request() subdomains
- nodejs/node#65617: http: match subdomains for plain NO_PROXY entries
- nodejs/node#66202: fetch() and http.request() with an empty lowercase variable
- Node.js 26.10.0 docs: NO_PROXY format
- Node.js 26.10.0 docs: NODE_USE_ENV_PROXY
- Node.js 26.10.0 source: lib/internal/http.js (http.request matcher)
- Node.js 26.10.0: bundled undici version
- undici PR #5777: NO_PROXY wildcard semantics
- undici v8.11.0 release notes
- undici PR #5623: match bare IPv6 addresses in no_proxy
- undici PR #5637: ignore trailing dots when matching no_proxy
- undici v8.11.0 docs: ProxyAgent
- undici v8.11.0 docs: EnvHttpProxyAgent
- libcurl: CURLOPT_NOPROXY
- curl changelog
- curl#19828: IPv6 CIDR notation in NO_PROXY (fixed in 8.18.0)
- Go: golang.org/x/net/http/httpproxy Config
- Go: httpproxy Config.ProxyFunc (localhost and loopback)
- golang.org/x/net commit a02ddfa7ea: httpproxy prefers lowercase proxy variables (x/net v0.58.0)
- golang/go#79656: inconsistent HTTP_PROXY precedence (milestone Go 1.28)
- Go 1.28 release-note draft: ProxyFromEnvironment prefers lowercase variables
- GNU Wget manual: Proxies
- Python 3.14 docs: urllib.request
- CPython 3.14.7 source: Lib/urllib/request.py (proxy_bypass by platform)
- Requests v2.34.0 release notes
- Requests v2.34.2 source: utils.py (should_bypass_proxies)
- Requests PR #7586: honor ports in IPv4 no_proxy entries
- httpx2 changelog (v2.13.1)
- httpx2 PR #1165: support IP CIDR ranges in NO_PROXY
- aiohttp docs: proxy support and trust_env
- aiohttp v3.14.3 source: helpers.py