Диагностика6 мин чтения

Переменные окружения прокси: HTTP_PROXY и NO_PROXY

Разбираем различия в маршрутизации по HTTP_PROXY, HTTPS_PROXY, ALL_PROXY и NO_PROXY в curl, Python Requests и Node.js с помощью изолированной локальной проверки.

На этой странице

С чего начать

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

Отделяйте выбор по назначению от соединения с прокси

Эти переменные — соглашения, которые каждый клиент интерпретирует по-своему, а не переключатель операционной системы, направляющий трафик всех приложений. Работающая команда curl не доказывает, что другая библиотека унаследовала или учла те же настройки.

Имя переменной обычно определяет схему целевого адреса. HTTPS_PROXY может содержать URL прокси вида http://: целевой сервер использует HTTPS, а клиент сначала подключается к HTTP-прокси и запрашивает туннель CONNECT. Схема в URL прокси описывает соединение с самим прокси.

  • http_proxy / HTTP_PROXY: выбор прокси для HTTP-назначения с учётом правил клиента о регистре.
  • https_proxy / HTTPS_PROXY: выбор прокси для HTTPS-назначения.
  • all_proxy / ALL_PROXY: запасной вариант в curl и Requests, когда не выбран подходящий прокси для конкретной схемы. Не предполагайте, что его реализует каждый клиент.
  • no_proxy / NO_PROXY: запрос прямого соединения для подходящих назначений, когда действует выбор прокси из окружения. Это исключение из маршрутизации, а не ещё один адрес прокси.

Определите клиент и версию, которые реально выполняют запрос

Локальные проверки маршрутизации для этого руководства выполнялись на curl 8.7.1, Requests 2.34.2 на Python 3.14.7 и Node.js 26.8.1. В примечаниях к выпуску Node.js 24.5.0 отдельно задокументировано появление поддержки прокси из окружения для стандартных агентов HTTP/HTTPS; fetch получил такую опциональную поддержку раньше, в 24.0.0. Эти исторические версии проверены по документации, а не запускались здесь.

  • curl принимает http_proxy в нижнем регистре, но намеренно игнорирует HTTP_PROXY в верхнем, поскольку в CGI-окружениях это имя может быть образовано из заголовка входящего запроса. HTTPS_PROXY и ALL_PROXY принимаются. Переменная для конкретного протокола имеет приоритет над ALL_PROXY.
  • Requests обычно читает все четыре семейства переменных, включая имена в верхнем регистре. Механизм обнаружения прокси в Python предпочитает нижний регистр, если значения в двух регистрах расходятся; в CGI-окружениях с установленным REQUEST_METHOD HTTP_PROXY в верхнем регистре игнорируется. Значения из окружения могут переопределять session.proxies, поэтому один словарь сессии не является границей изоляции.
  • Встроенная поддержка окружения в Node требует явного включения: запускайте процесс с NODE_USE_ENV_PROXY=1. Тогда стандартные агенты HTTP/HTTPS и нативный fetch используют HTTP_PROXY, HTTPS_PROXY и NO_PROXY; нижний регистр имеет приоритет. Одного ALL_PROXY в этом встроенном режиме недостаточно. Пользовательские агенты, диспетчеры и сторонние пакеты требуют отдельной проверки конфигурации.

Относитесь к NO_PROXY как к изменению маршрута

Начните со списка точных разрешённых целевых хостов через запятую, затем проверьте, какие запросы реально под него подпадают. Не вставляйте в список хостов полные URL или пути. Подстановочный знак * запрашивает прямой маршрут для всех назначений во всех рассматриваемых здесь клиентах; он не годится как быстрое «решение» для обязательного прокси.

Нет переносимой грамматики сопоставления, на которую можно рассчитывать во всех этих клиентах. Например, curl документирует сопоставление по CIDR начиная с 7.86.0; во встроенной документации Node перечислены имена хостов, суффиксы доменов, диапазоны адресов и записи host:port. Запись домена, подстановочный знак или подсеть, работающие в одном клиенте, требуют отдельной проверки в другом. DNS-псевдонимы и IP-литералы тоже меняют имя, с которым идёт сопоставление.

Если внутренний запрос неожиданно идёт через прокси, изучите фактическое имя целевого хоста и правила сопоставления конкретного клиента. Если внешний запрос неожиданно идёт напрямую, проверьте NO_PROXY в обоих регистрах и любую явную конфигурацию агента. Меняйте только то правило маршрутизации, которое утверждено для данного развёртывания.

Наблюдайте выбор прокси без реального шлюза

Сохраните этот файл как proxy_env_lab.py и запустите python3 proxy_env_lab.py при установленном curl. Он поднимает два HTTP-сервера-маркера на случайных loopback-портах: один представляет прямое назначение, другой — выбранный прокси. Маркер прокси отвечает локально; он не пересылает трафик и не проверяет CONNECT, TLS, аутентификацию или исходящий IP-адрес.

Каждый процесс curl получает только переданное тестовое окружение. --disable стоит первой опцией, чтобы личный curlrc не мог повлиять на эксперимент. Ожидаемые результаты: proxy, direct, direct, proxy. Это показывает, что даже явный --proxy подчиняется NO_PROXY, пока список обхода не очищен явно для этой локальной диагностики.

proxy_env_lab.py · стандартная библиотека Python 3 + curl

python
import shutil
import subprocess
import threading
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer

curl = shutil.which("curl")
if not curl:
    raise SystemExit("Install curl before running this local lab")

def marker(label):
    class Handler(BaseHTTPRequestHandler):
        def do_GET(self):
            body = label.encode()
            self.send_response(200)
            self.send_header("Content-Length", str(len(body)))
            self.end_headers()
            self.wfile.write(body)

        def log_message(self, *_):
            pass

    server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
    threading.Thread(target=server.serve_forever, daemon=True).start()
    return server, "http://127.0.0.1:" + str(server.server_port)

origin, destination = marker("direct")
proxy_server, proxy = marker("proxy")
cases = [
    ("environment", {"http_proxy": proxy}, [], "proxy"),
    ("host bypass", {"http_proxy": proxy, "NO_PROXY": "127.0.0.1"}, [], "direct"),
    ("explicit + bypass", {"NO_PROXY": "127.0.0.1"}, ["--proxy", proxy], "direct"),
    ("explicit + cleared bypass", {"NO_PROXY": "127.0.0.1"},
     ["--proxy", proxy, "--noproxy", ""], "proxy"),
]
try:
    for label, environment, options, expected in cases:
        result = subprocess.run(
            [curl, "--disable", "--silent", "--show-error", "--fail",
             "--connect-timeout", "2", "--max-time", "3", *options, destination],
            env=environment, capture_output=True, text=True, timeout=5,
        )
        if result.returncode or result.stdout != expected:
            raise SystemExit("Local route check failed: " + label)
        print(label + ": " + result.stdout)
finally:
    for server in (origin, proxy_server):
        server.shutdown()
        server.server_close()

Задайте базовую конфигурацию приложения осознанно

Для диагностики через curl с обязательным прокси используйте явный --proxy вместе с --noproxy '', как показано в существующем руководстве по настройке curl. Для Requests передавайте аргумент proxies в самом запросе; используйте отдельную Session с trust_env=False, когда базовая конфигурация должна игнорировать унаследованные настройки прокси. Это также отключает аутентификацию и конфигурацию CA-bundle, получаемые из окружения, поэтому при необходимости явно передавайте утверждённый пользовательский набор доверенных сертификатов.

Для Node выберите либо проверенное включение через окружение, либо явный совместимый агент/диспетчер. Не предполагайте, что пользовательский агент библиотеки наследует глобальную настройку. Уточните, какой именно runtime используется воркером или сервисом, а не только версию, установленную в интерактивном терминале.

Когда локальное правило понятно, сравните одно разрешённое назначение с одним шлюзом из документации провайдера. Держите проверку TLS включённой, задайте дедлайн и подтвердите путь запроса контролируемыми наблюдениями на конечной точке или прокси. Успешный HTTP-статус сам по себе не показывает, какой маршрут был выбран.

Записывайте решения, не записывая секреты

Сравните окружение процесса, выполняющего запрос, с конфигурацией его запускающего процесса, контейнера или сервиса. Фиксируйте имена переменных и факт их установки, версию клиента, схему назначения и наблюдаемый маршрут. Избегайте дампов всего окружения, трассировки shell, подробных протокольных логов и сырого текста исключений: URL прокси может содержать пароль.

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

Перед выпуском

  • Определите фактический клиент, версию runtime и схему назначения.
  • Проверьте имена переменных в верхнем и нижнем регистре, не логируя их значения.
  • Проверьте сопоставление NO_PROXY и выбранный маршрут на разрешённом назначении.
  • Сравните контролируемое окружение с явной конфигурацией, прежде чем добавлять повторы.

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

Технические материалы, использованные при подготовке руководства. Сверяйтесь с документацией вашей версии и с поддерживаемой конфигурацией вашего провайдера.