Ошибка 407 при работе через прокси: что означает и как её снять
Код 407 означает, что узел-посредник принял соединение и отказался пропускать запрос дальше, пока клиент не подтвердит право доступа. Отказ приходит от самого прокси, целевой сайт этого обращения ещё не видел, поэтому чинится всё на стороне подключения: привязка адреса, пара логина и пароля, формат строки, схема протокола.
Ниже разобрано, чем 407 отличается от похожего на вид 401, как читается ответ с заголовком Proxy-Authenticate, шесть причин в порядке от частой к редкой и что делать, когда отказ авторизации приходит внутри сокетного соединения, где кода 407 не бывает вовсе.
От кого приходит 407 и почему он появляется так рано
Разберём путь запроса по шагам. Программа открывает соединение с узлом на указанном порту, отправляет ему запрос, узел смотрит на источник и на переданные учётные данные, и только после этого решает, идти ли дальше к целевому домену. Отказ 407 выносится на втором шаге. Целевой сервер в этот момент ещё ничего не получил, его журналы пусты, его настройки к делу отношения не имеют.
Отсюда первое практическое следствие: 407 воспроизводится на любом адресе назначения. Мы проверяем это одной командой, подставив вместо рабочего домена нейтральный эхо-сервис. Если отказ повторился, вопрос точно в доступе, и дальше остаётся перебрать шесть причин по списку. Если на эхо-сервисе прошло, а на рабочем домене нет, это уже другая история и другой код ответа.
Второе следствие касается сетевого уровня. Раз 407 пришёл, соединение до узла установилось: маршрут рабочий, порт открыт, фильтры пропустили. Отказ на уровне сети выглядит совсем иначе, там нет ни кода, ни тела ответа, соединение обрывается или висит до таймаута. Мы держим это разделение в голове при каждом разборе, потому что оно сразу отсекает половину бесполезных проверок.
Третье следствие про темп. Отказ приходит мгновенно, за первые миллисекунды. Ждать нечего.
Чем 407 отличается от 401: два разных отказа
Оба кода говорят про подтверждение права доступа, оба сопровождаются заголовком-подсказкой, оба выглядят в консоли одинаково коротко. Разница сидит в том, кто их выписал. Код 401 приходит от целевого сайта, который защищает свой раздел паролем. Код 407 приходит от посредника, который защищает право пользоваться пулом.
| Признак | 407 | 401 |
|---|---|---|
| Источник ответа | Узел-посредник | Целевой сайт |
| Заголовок в ответе | Proxy-Authenticate | WWW-Authenticate |
| Заголовок в запросе | Proxy-Authorization | Authorization |
| Воспроизводится на любом домене | Да | Нет, только на своём разделе |
| Приходит до туннеля CONNECT | Да | Нет, приходит после установки туннеля |
| Где чинится | Строка подключения и кабинет | Учётная запись на целевом сайте |
Путаница между кодами стоит дороже всего в скриптах, где обработчик ошибок написан по одной ветке. Программа получила 407, обработчик решил, что не хватает пароля от сайта, подставил в запрос заголовок Authorization и отправил заново. Узел снова ответил 407, потому что нужный ему заголовок так и не появился. Цикл повторяется до исчерпания попыток, а в журнале скрипта висит невнятная строка про отказ авторизации.
Есть и обратная ситуация. Через посредник открывается раздел, закрытый паролем: узел пропустил запрос, сайт ответил 401. Здесь настройки прокси в порядке, разбирать нужно учётную запись на стороне сайта. Различить эти два случая помогает подробный вывод: в первом стоит Proxy-Authenticate, во втором WWW-Authenticate.
Как выглядит полный ответ с заголовком Proxy-Authenticate
Сырой ответ узла короткий. Строка статуса, заголовок со схемой подтверждения, служебные поля, крошечное тело или полное его отсутствие. Заголовков целевого сайта в нём нет вовсе, и это самый быстрый способ понять, кто ответил.
HTTP/1.1 407 Proxy Authentication Required
Proxy-Authenticate: Basic realm="proxy"
Proxy-Connection: close
Content-Length: 0
Поле Proxy-Authenticate говорит, какую схему узел готов принять. Базовая схема Basic означает пару логина и пароля, закодированную в base64. Значение realm это просто имя области доступа, оно ни на что не влияет и в запрос не переносится. Поле Proxy-Connection: close показывает, что узел закрывает соединение сразу после отказа, и следующая попытка пойдёт по новому соединению.
Ответ клиента на такой отказ выглядит одной строкой в заголовках запроса:
GET http://example.com/ HTTP/1.1
Host: example.com
Proxy-Authorization: Basic dXNlcjU1MjE6cGYzOWtk
User-Agent: curl/8.5.0
Значение после слова Basic это база64 от строки логин:пароль. Раскодировать её можно на месте, и это первая проверка при разборе спорного случая: расшифрованная строка сверяется с парой из кабинета символ в символ.
# что реально ушло в заголовке
echo -n 'dXNlcjU1MjE6cGYzOWtk' | base64 -d
# user5521:pf39kd
# как собрать значение самому и сверить
printf '%s' 'user5521:pf39kd' | base64
Шесть причин 407 в порядке от частой к редкой
Причины идут по убыванию частоты обращений, которые мы разбираем. Порядок стоит соблюдать: верхние проверяются за минуту, нижние требуют захода в кабинет.
Доступ открыт по привязке, запрос пришёл с другого адреса
Самая частая причина вообще не связана с паролем. В кабинете указан адрес рабочей машины, узел сверяет источник соединения со списком доступа и не находит совпадения. Пары логина и пароля в запросе тоже нет, потому что список брали в коротком формате IP:PORT. Узлу остаётся ответить отказом и попросить учётные данные, которых у клиента нет.
Признак простой: доступ работал вчера и перестал сегодня, настройки софта никто не трогал. Провайдер выдал новый адрес после переподключения, скрипт переехал на другой сервер, запуск пошёл из контейнера с отдельным сетевым интерфейсом. Мы начинаем разбор именно с этого: сверяем текущий внешний адрес машины с тем, что вписан в настройках.
# внешний адрес машины, с которой уходит запрос
curl -s https://ifconfig.me
# сверяем результат со строкой в кабинете
Порядок действий: открыть настройки пакета, переписать привязку на текущий адрес, подождать применения и повторить запрос. Привязку разрешено менять без ограничений, в пакет входит одновременная привязка 2 адресов, поэтому рабочая машина и сервер спокойно живут вместе. При двух привязанных адресах общий лимит потоков делится между ними пополам, эту цифру полезно помнить при расчёте нагрузки. Механика самой привязки подробно разобрана в отдельном материале раздела подключения.
Логин и пароль не переданы вовсе
Вторая причина выглядит как первая, лечится иначе. Доступ настроен по паре логина и пароля, программа об этом не знает и отправляет голый запрос. Такое случается, когда список забрали в формате IP:PORT, а нужен был формат IP:PORT:LOGIN:PASS. Оба формата доступны в кабинете, и берётся тот, который соответствует выбранному способу доступа.
Второй частый вариант: пара есть, программа её теряет. Часть парсеров хранит адрес и учётные данные в разных полях, и при импорте четырёхчастной строки последние два поля отбрасываются молча. Проверяется это глазами: открыть настройки прокси в программе и убедиться, что поля логина и пароля заполнены.
# короткий формат, узел ждал пару
curl -x http://185.24.87.14:8000 https://ifconfig.me
# полный формат, пара внутри строки
curl -x http://user5521:[email protected]:8000 https://ifconfig.me
# та же пара отдельным ключом, разбора адресной строки не происходит
curl -x http://185.24.87.14:8000 -U user5521:pf39kd https://ifconfig.me
Ключ -U мы советуем как основной способ проверки. Он снимает сразу два класса ошибок: разбор служебных символов и потерю части строки при копировании. Если с ним запрос проходит, значит пара верна, и вопрос сидит в записи строки. Полный разбор форматов авторизации для браузерного и программного трафика собран там, где оформляется доступ к прокси HTTP с парой логина и пароля.
Спецсимволы в пароле не экранированы
Пароль попадает внутрь адресной строки, и часть символов там имеет служебное значение. Знак @ разделяет учётные данные и хост, двоеточие разделяет логин и пароль, косая черта начинает путь, # открывает якорь, ? начинает параметры, % начинает процентную запись. Пароль p@ss:1 внутри строки разбирается совсем не так, как задумано, и до узла доезжает обрезок.
Признак характерный: пара скопирована из кабинета правильно, отказ приходит стабильно, а тот же пароль в отдельном поле программы работает. Ещё один признак: в подробном выводе видно, что curl пытается соединиться с хостом, имя которого собрано из куска пароля.
| Символ | Что он значит в адресной строке | Процентная запись |
|---|---|---|
@ | Граница между парой и хостом | %40 |
: | Граница между логином и паролем | %3A |
/ | Начало пути | %2F |
# | Начало якоря | %23 |
? | Начало параметров запроса | %3F |
% | Начало процентной записи | %25 |
& | Разделитель параметров | %26 |
# кодируем пароль перед подстановкой
python3 -c "import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1], safe=''))" 'p@ss:1'
# p%40ss%3A1
curl -x 'http://user5521:p%40ss%[email protected]:8000' https://ifconfig.me
Разбор строки подключения по полям, включая схему, порт и служебные символы, вынесен в материал про строку подключения и её поля.
Учётные данные уходят только после первого отказа
Схема Basic предполагает диалог: клиент отправляет запрос, получает 407, повторяет запрос с заголовком Proxy-Authorization. Так работает браузер, так работает curl. Часть библиотек этот второй шаг не делает: они видят код 407, поднимают исключение и возвращают его в код скрипта, а пара так и остаётся в настройках нетронутой.
Признак узнаётся по журналу: в трассировке видно ровно один запрос без заголовка Proxy-Authorization и один ответ 407. Повторного запроса нет. Лечится это упреждающей отправкой пары: заголовок собирается вручную и ставится в первый же запрос.
import base64, requests
pair = base64.b64encode(b"user5521:pf39kd").decode()
headers = {"Proxy-Authorization": "Basic " + pair}
proxies = {"http": "http://185.24.87.14:8000",
"https": "http://185.24.87.14:8000"}
r = requests.get("https://ifconfig.me", proxies=proxies, headers=headers, timeout=15)
print(r.status_code, r.text)
Отдельный случай это туннель CONNECT для запросов по HTTPS. Заголовок Proxy-Authorization должен уйти внутри самого запроса CONNECT, до согласования шифрования. Библиотеки, которые ставят заголовки только в прикладной запрос, до узла их не доносят: узел читает строку CONNECT, пары там не видит и отвечает 407 ещё до подъёма туннеля. В таких случаях пара задаётся штатным полем библиотеки, где она вписывается прямо в адрес прокси. Устройство самого туннеля разобрано в разделе про протоколы.
В строке подключения указана неверная схема
Схема в начале строки говорит программе, по какому протоколу разговаривать с узлом. Значения http, https, socks4, socks5 и socks5h дают четыре разных диалога. Когда к сокетному порту обращаются по схеме http, узел получает набор байт, который не разбирает, и отвечает отказом. Обратная запись даёт ту же картину.
| Схема в строке | С каким узлом разговаривает | Где разрешается имя домена |
|---|---|---|
http:// | Порт HTTP, обычные запросы и CONNECT | На стороне клиента |
https:// | Порт HTTP через шифрованный канал до узла | На стороне клиента |
socks4:// | Сокетный порт, старый вариант протокола | На стороне клиента |
socks5:// | Сокетный порт с подтверждением доступа | На стороне клиента |
socks5h:// | Тот же сокетный порт | На стороне узла |
Признак: отказ приходит на всех адресах списка сразу, при этом на соседнем порту того же узла всё проходит. Мы советуем держать в заметках две проверенные строки, одну под HTTP-порт и одну под сокетный, и подставлять их при любом сомнении. В пакете доступны оба варианта одновременно, докупать ничего не требуется, и переход сводится к правке одной строки. Сокетная часть подробно описана там, где берётся пакет с доступом по SOCKS5.
Учётная запись или пакет не активны
Последняя причина редкая, зато снимается за минуту. Срок доступа истёк, пакет ещё не включился после оплаты, учётная запись поставлена на паузу до выяснения из-за подозрительно высокой активности. Узел в таком случае отвечает тем же 407, потому что предъявленные учётные данные он больше не признаёт.
Признак однозначный: строка подключения прежняя, привязка на месте, пара верна, отказ при этом приходит на всех адресах и по всем схемам сразу. Смотрим в кабинет: статус пакета, срок доступа, состояние учётной записи. Включение пакета после оплаты занимает примерно 5 минут, и запрос, отправленный в эту паузу, тоже получит отказ. Если статус в кабинете рабочий, а отказ остался, пишем оператору по контактам с сайта и передаём сразу логин и тип прокси, тогда разбор идёт одним сообщением.
Разбор обмена через curl -v: что смотреть построчно
Подробный вывод показывает весь диалог целиком, включая заголовки в обе стороны. Строки со стрелкой вправо это то, что ушло от клиента, строки со стрелкой влево это ответ узла. Для разбора 407 нужны обе стороны сразу.
curl -v -x http://user5521:[email protected]:8000 https://example.com -o /dev/null
* Connected to 185.24.87.14 (185.24.87.14) port 8000
* CONNECT tunnel: HTTP/1.1 negotiated
* Proxy auth using Basic with user 'user5521'
> CONNECT example.com:443 HTTP/1.1
> Host: example.com:443
> Proxy-Authorization: Basic dXNlcjU1MjE6cGYzOWtk
> User-Agent: curl/8.5.0
>
< HTTP/1.1 407 Proxy Authentication Required
< Proxy-Authenticate: Basic realm="proxy"
< Proxy-Connection: close
* CONNECT tunnel failed, response 407
Читаем сверху вниз. Строка Connected to подтверждает, что до узла дошли. Строка Proxy auth using Basic with user показывает, какой логин взят из строки: если там пусто или стоит обрезок, вопрос в записи пароля. Строка Proxy-Authorization показывает, что заголовок реально ушёл: её отсутствие означает, что программа пару не отправила. Ответ 407 с полем Proxy-Authenticate подтверждает источник отказа.
Отдельно полезен ключ, который печатает сырой обмен байтами. Он показывает то, что уходит в сеть, без промежуточной обработки, и снимает споры о том, потерялась пара в библиотеке или в строке.
# сырой обмен, включая тело запроса CONNECT
curl -v --trace-ascii - -x http://185.24.87.14:8000 -U user5521:pf39kd https://example.com -o /dev/null
# только код ответа, для быстрого перебора строк списка
curl -s -o /dev/null -w '%{http_code}\n' -x http://185.24.87.14:8000 -U user5521:pf39kd https://ifconfig.me
Быстрый перебор пригодится, когда список большой и отказ приходит выборочно. Прогоняем строки циклом, собираем коды и смотрим на долю отказов. Отказ на всех строках указывает на доступ, отказ на единичных строках указывает на устаревший список: он обновляется в реальном времени, поэтому перед крупным прогоном его стоит перечитать. Порты и схемы, которые принимают узлы по протоколу HTTP, перечислены там, где берётся прокси HTTP для браузеров и парсеров.
Отказ авторизации внутри SOCKS5: там свой код
Кода 407 в сокетном протоколе нет вовсе. Он относится к HTTP, а сокетный диалог идёт двоичными байтами и своим порядком согласования. Клиент присылает список поддерживаемых способов подтверждения доступа, узел выбирает один и отвечает его номером. Значение 0x00 означает, что подтверждение не требуется, значение 0x02 означает пару логина и пароля, значение 0xFF означает отказ: ни один из предложенных способов узел не принял.
| Байт от узла | Что означает | Что делать |
|---|---|---|
0x00 | Доступ по привязке, пара не нужна | Работать по короткому формату |
0x02 | Узел требует пару логина и пароля | Взять формат IP:PORT:LOGIN:PASS |
0xFF | Ни один предложенный способ не принят | Включить поддержку пары в клиенте |
Статус 0x01 в ответе на пару | Пара предъявлена и отклонена | Сверить логин и пароль с кабинетом |
В curl эта картина выражается кодом завершения 97 и текстом про отклонённое рукопожатие. Он приходит вместо 407 и означает то же самое по смыслу: право доступа не подтверждено.
curl -v -x socks5h://user5521:[email protected]:1080 https://ifconfig.me
# curl: (97) User was rejected by the SOCKS5 server (1 1).
echo $? # 97
Отдельная тонкость сокетного варианта: часть старых клиентов умеет только способ 0x00 и пару передавать не умеет физически. Такой клиент получает 0xFF при любых верных учётных данных. Здесь помогает переход на доступ по привязке: адрес вписывается в кабинет, пара из строки убирается, диалог сходится на 0x00. Оба способа доступа входят в пакет, и переключение между ними ничего не стоит. Устройство обоих вариантов разобрано в статье про два способа доступа к пулу.
Соседние коды: с чем 407 путают чаще всего
Таблица собрана по обращениям, которые к нам приходят с формулировкой «прокси не работает». Кода в ней достаточно, чтобы определить источник отказа и следующий шаг.
| Код | Кто прислал | Что означает | Первое действие |
|---|---|---|---|
| 407 | Узел-посредник | Право доступа не подтверждено | Сверить привязку и пару логина с паролем |
| 401 | Целевой сайт | Раздел сайта закрыт паролем | Проверить учётную запись на сайте |
| 403 | Целевой сайт | Запрос принят и отклонён по содержанию | Смотреть темп, заголовки и отпечаток |
| 429 | Целевой сайт | Частота обращений выше принятой | Снизить темп, развести запросы по пулу |
| 502 | Узел-посредник | Запрос принят, до площадки не дошёл | Повторить, взять другую строку списка |
| 503 | Целевой сайт или защита перед ним | Приём запросов временно закрыт | Отложить прогон, проверить страницу глазами |
| 504 | Узел-посредник | Площадка не ответила в отведённое время | Поднять таймаут, проверить домен напрямую |
Коды завершения самой программы дополняют картину и часто отвечают раньше, чем чтение заголовков.
| Код curl | Название | Что означает при разборе 407 |
|---|---|---|
| 7 | Failed to connect | До узла не дошли, авторизация тут ни при чём |
| 28 | Operation timed out | Ответа нет, отказ авторизации выглядит иначе |
| 56 | Recv failure | Соединение сброшено, источник вне списка доступа |
| 97 | Proxy handshake failed | Сокетный аналог отказа авторизации |
Различие между 407 и 403 стоит зафиксировать отдельно, потому что эти два кода путают чаще прочих. Отказ 407 приходит до целевого сайта и воспроизводится на любом домене, включая нейтральный эхо-сервис. Отказ 403 приходит от площадки после того, как узел запрос пропустил, и на эхо-сервисе его не будет никогда. Разбор второго кода по слоям вынесен в соседнюю статью раздела диагностики.
Порядок разбора, когда 407 пришёл на рабочем пакете
Ситуация «вчера работало, сегодня 407» разбирается по фиксированной цепочке. Мы идём от того, что меняется чаще всего, к тому, что меняется реже.
| Шаг | Что проверяем | Чем проверяем | Что означает совпадение |
|---|---|---|---|
| 1 | Внешний адрес машины | curl -s https://ifconfig.me | Адрес сменился, привязка устарела |
| 2 | Статус пакета в кабинете | Раздел пакетов | Срок доступа истёк или включение не завершилось |
| 3 | Формат списка | Раздел выдачи | Взят короткий формат при доступе по паре |
| 4 | Отправку заголовка | curl -v, строка Proxy-Authorization | Программа пару не отправила |
| 5 | Запись пароля | Ключ -U вместо адресной строки | Спецсимвол сломал разбор строки |
| 6 | Схему в строке | Сверка порта и схемы | Обращение ушло на порт другого протокола |
Первые три шага занимают минуту и закрывают большую часть обращений. Шаги с четвёртого по шестой нужны там, где доступ настраивается в программе со своими полями и своей библиотекой запросов.
Отдельно про массовые прогоны. Отказ 407 на части потоков при рабочей строке подключения указывает на превышение лимита: стандартные пакеты дают до 1000 потоков, корпоративный до 3000, пакеты по потокам не складываются. При двух привязанных адресах цифра делится пополам, и расчёт, сделанный по полному лимиту, упирается в потолок посреди прогона. Мы советуем считать нагрузку по фактическому лимиту и оставлять запас примерно в треть. Состав пакета по потокам, привязкам и трафику описан там, где берутся приватные серверные адреса для рабочей группы.
И последнее про повторные попытки. Отправлять тот же самый запрос заново смысла нет: узел вынес отказ по содержанию запроса, содержание не изменилось, ответ будет прежним. Повтор оправдан ровно в одном случае: после правки привязки в кабинете, когда изменение применяется с небольшой задержкой. Во всех остальных случаях меняется строка, формат или настройка, и только потом отправляется новая попытка. Полный набор портов и протоколов, которые принимают узлы пула, собран там, где оформляется пакет адресов IPv4 на все протоколы.
Что делать, чтобы 407 не появлялся на новых машинах
Повторяемость снимается тремя привычками. Первая: строка подключения хранится в одном месте и подставляется из переменной окружения, без ручного переписывания в каждом скрипте. Вторая: перед запуском прогона идёт короткая проверка доступа одной командой, и прогон стартует только после кода 200. Третья: список адресов перечитывается из кабинета перед каждым крупным запуском.
# одна строка на всю машину
export HTTP_PROXY='http://user5521:[email protected]:8000'
export HTTPS_PROXY="$HTTP_PROXY"
export NO_PROXY='localhost,127.0.0.1'
# проверка перед стартом прогона
code=$(curl -s -o /dev/null -w '%{http_code}' https://ifconfig.me)
[ "$code" = "200" ] && echo "доступ есть" || echo "доступ отказан, код $code"
Переменная NO_PROXY избавляет от лишних обращений к узлу с локальных адресов. Без неё часть служебных запросов уходит в пул и возвращает отказ, который к рабочей задаче отношения не имеет. Мы держим эту строку в стартовом скрипте каждого сервера.
Переезд на новую машину проходит по тому же короткому списку: вписать её внешний адрес в кабинет либо взять формат с парой логина и пароля, поставить переменные окружения, прогнать проверку. При доступе по паре машину вообще не нужно вписывать никуда, и это самый спокойный вариант для контейнеров и облачных запусков, где адрес меняется от старта к старту. Как устроены сами узлы, на которых держится пул, описано на странице про серверные адреса на собственном оборудовании.
Частые вопросы
Почему 407 приходит, хотя логин и пароль скопированы верно?
Чаще всего пара теряется по дороге. Спецсимвол внутри адресной строки обрывает разбор, программа раскладывает четырёхчастную строку по своим полям и отбрасывает часть, библиотека не повторяет запрос после первого отказа. Проверяется это ключом -U в curl: он передаёт пару отдельно от адреса, и если так запрос проходит, вопрос сидит в записи строки.
Сколько адресов можно привязать к пакету и мешает ли это авторизации?
В стоимость входит одновременная привязка 2 адресов, менять их разрешено без ограничений прямо в настройках кабинета. При двух привязанных адресах общее число потоков делится между ними пополам. Сама авторизация от числа привязок не зависит, отказ приходит только тогда, когда источник запроса отсутствует в списке доступа.
В каком формате выдаётся список прокси?
Форматов два: IP:PORT для работы с привязанным адресом и IP:PORT:LOGIN:PASS для доступа по паре логина и пароля. Забрать список можно ссылкой или файлом, оба варианта лежат в кабинете. Список обновляется в режиме реального времени, поэтому перед прогоном его полезно перечитать заново.
Что делать, если учётная запись не активируется?
Написать по контактам с сайта, там помогут запустить. Оператору сразу передаётся логин и тип прокси, тогда ответ приходит одним сообщением. Пакет после оплаты включается примерно за 5 минут, и запрос, отправленный в эту паузу, тоже получит отказ авторизации.
Соседние разборы диагностики собраны рядом: проверка работы прокси командами и в браузере с полным набором исходов, отказ 403 от целевой площадки по слоям причин, замер скорости и времени отклика без искажений от кеша. Когда доступ восстановлен и запросы проходят, следующий шаг описан в материале про подключение в программах и на сервере.