Cómo solucionar HTTP 407 Proxy Authentication Required

Publicado:

19 min de lectura

Acar Diveroli
Autor: Acar Diveroli
Ante una puerta PROXY, las tarjetas NO AUTH y WRONG PASS paran en una cruz roja; una tarjeta azul Proxy-Authorization pasa

Pegas en cURL un proxy recién comprado y la respuesta es 407 Proxy Authentication Required. Los mismos tres dígitos aparecen en Python como Tunnel connection failed: 407, en Git como The requested URL returned error: 407 y en Chrome como una ventana de usuario que no deja de reaparecer. Todos tienen un mismo origen: tu petición nunca llegó al sitio web. El proxy que hay en medio la detuvo en la puerta.

Este artículo explica qué significa el 407 en el estándar HTTP, en qué se diferencia del 401 y qué falta en esa puerta. Después recorre los lugares donde aparece el error: credenciales erróneas o ausentes en un proxy comprado, una IP doméstica que cambió con la autenticación por IP, un carácter especial en la contraseña, Chrome rechazando la forma user:pass@, proxies NTLM y Kerberos en redes corporativas, y dónde van las credenciales en cURL, Python, Node.js, npm, pip, Git y Docker. Ejecutamos todos los ejemplos de código el 7 de octubre de 2026 contra un proxy de prueba local que exige usuario y contraseña.

¿Qué significa HTTP 407 Proxy Authentication Required?

Un 407 es el proxy diciendo «demuestra quién eres antes de que reenvíe esto». El estándar HTTP, el RFC 9110, define el código en la sección 15.5.8: el 407 es similar al 401, pero indica que el cliente necesita autenticarse para usar un proxy en esta petición. El proxy debe enviar una cabecera Proxy-Authenticate con un desafío aplicable a ese proxy, y el cliente puede repetir la petición con una cabecera Proxy-Authorization nueva o sustituida.

En la práctica, el intercambio transcurre entre esas dos cabeceras. El proxy nombra el esquema con Proxy-Authenticate: Basic realm="x"; en Basic, el esquema más común con diferencia, el cliente une usuario y contraseña como user:pass, codifica la cadena en Base64 y añade Proxy-Authorization: Basic dXNlcjpwYXNz. Base64 no es cifrado; cualquiera que decodifique dXNlcjpwYXNz lee user:pass, así que las credenciales dependen de una red de confianza o de TLS en el camino hasta el proxy.

Un detalle del estándar ayuda al depurar: Proxy-Authorization lo consume el primer proxy que lo recibe y no se reenvía al sitio web. El proxy ve tu contraseña; el sitio no. Cuando varios proxies forman una cadena en una red corporativa, cada uno exige sus propias credenciales, y el valor realm de Proxy-Authenticate te dice cuál envió el 407.

¿Qué diferencia hay entre 407 y 401?

Los dos códigos ejecutan el mismo mecanismo en dos partes distintas. 401 Unauthorized es el sitio web pidiendo credenciales: la respuesta lleva WWW-Authenticate, el cliente responde con Authorization y el problema está entre tú y el sitio. En un 407 el mismo diálogo ocurre con el proxy, mediante Proxy-Authenticate y Proxy-Authorization, y el sitio web no ha visto la petición en absoluto.

Esa distinción decide dónde mirar. Tras un 401 revisas tu cuenta, tu clave de API o tu token en el sitio. Tras un 407 nada del sitio importa; el usuario, la contraseña o la lista de IP del panel del proxy, o la cuenta que te dio el administrador de red, es donde vive el problema. También puedes recibir los dos seguidos: primero el proxy se pasa con un 407 y luego el sitio devuelve 401.

¿Cómo se produce un 407, paso a paso?

Para observar el flujo escribimos un proxy de prueba de 40 líneas en Node.js. Responde a toda petición cuya cabecera Proxy-Authorization no coincida con user:pass con un 407 y Proxy-Authenticate: Basic realm="x", reenvía las peticiones que coinciden, aplica la misma regla al túnel CONNECT que usa HTTPS y registra cada petición. Para una página HTTPS, el registro se leía así:

  1. El cliente envía CONNECT site.com:443 al proxy, pidiendo un túnel hasta el sitio. Para una dirección http:// envía la petición en sí (GET http://site.com/ …).
  2. Si la petición no lleva Proxy-Authorization, el proxy devuelve 407 Proxy Authentication Required con Proxy-Authenticate: Basic realm="x" y no abre nada. La línea del registro dice CONNECT site.com:443 Proxy-Authorization: (ninguna).
  3. El cliente añade la cabecera y repite la petición: Proxy-Authorization: Basic dXNlcjpwYXNz. Muchas herramientas lo hacen en el primer intento; cURL con -U nunca espera al 407.
  4. El proxy comprueba el valor. Si es correcto, abre el túnel y devuelve 200 Connection Established; a partir de aquí el tráfico TLS fluye por el túnel. Si es incorrecto, repite el paso 2; una contraseña errónea y una contraseña ausente son el mismo 407 para un proxy.
  5. El sitio web ve la petición solo ahora y da su propia respuesta (200, 404, 401…). Las cabeceras que recibió no incluían Proxy-Authorization; el proxy la consumió.

Con cURL, el primer paso de este flujo se ve en un solo comando. Para una dirección HTTP simple, una petición sin autenticar recibió esta respuesta:

bash
curl -i -x http://pr.proxynet.io:8000 http://example.com/
text
HTTP/1.1 407 Proxy Authentication Required
Proxy-Authenticate: Basic realm="x"
Connection: close

Para una dirección HTTPS, cURL informa en su lugar de su propio error: curl: (56) CONNECT tunnel failed, response 407. Ambos son la misma cabecera ausente.

¿Por qué ocurre el error 407?

Las causas se dividen en dos grupos: las credenciales de un proxy que compraste y las credenciales del proxy que opera tu red. El texto del error es idéntico en ambos; el lugar donde arreglarlo, no.

CausaSíntomaSolución
Las credenciales nunca se envíanSin línea Proxy-Authorization, 407 en la primera peticiónUsa la opción de credenciales de proxy de la herramienta (-U, proxies=, token)
Usuario o contraseña erróneosCabecera presente, sigue el 407Copia del panel; revisa espacios y mayúsculas
@, : o # en la contraseñaURL «bad hostname» o 407Codifica como %40, %3A, %23 en la URL, o usa una opción aparte
Autenticación por IP, cambió la IP domésticaLa configuración de ayer falla hoyAñade la nueva IP en el panel o pasa a usuario y contraseña
Chrome ignora user:pass@ en la direcciónERR_NO_SUPPORTED_PROXIES o una ventana de inicio de sesiónpage.authenticate() en Puppeteer, la ventana en el navegador
El proxy corporativo quiere NTLM o KerberosProxy-Authenticate: Negotiate o NTLMCuenta de dominio con --proxy-negotiate; NTLM está desactivado en las compilaciones de cURL desde la 8.20
Una cadena de proxiesLa ventana reaparece sin pararCada proxy quiere sus propias credenciales; pregunta al equipo de red

¿Cómo solucionar un 407 en un proxy comprado?

La mayoría de los 407 vienen de una pequeña diferencia entre lo que copiaste del panel y el formato que espera la herramienta. Primero comprueba si las credenciales se envían siquiera: si curl -v no muestra ninguna línea > Proxy-Authorization:, el problema es el formato. Si la línea está y la respuesta sigue siendo 407, el valor es incorrecto o la cuenta tiene un problema; en nuestra prueba el proxy respondió a una contraseña errónea exactamente igual que a ninguna contraseña.

Caracteres especiales. Una dirección de proxy es una URL de la forma http://user:pass@host:port, y en una URL el signo @ separa la información de usuario del host. Si tu contraseña es p@ss, la dirección contiene dos signos @; cURL 8.5 la rechazó con curl: (5) Unsupported proxy syntax … Bad hostname. Con %40 en lugar de la @ (http://user:p%40ss@host:port), el proxy decodificó user:p@ss y la petición pasó. Usa %3A para :, %23 para # y %2F para /. Python Requests aceptó también la dirección sin codificar, porque dividió la URL en la última @, pero codificar funciona igual en todas las herramientas. Las opciones que reciben las credenciales fuera de la URL (curl -U "user:p@ss", HTTPProxyAuth("user", "p@ss") en Requests) no necesitan codificación; ambas pasaron con la contraseña tal cual.

Autenticación por IP. Si registraste tu dirección IP en el panel en lugar de una contraseña, el proxy mira la dirección desde la que llega la conexión. En una conexión doméstica, la IP pública cambia cuando el módem se reinicia o el operador rota su rango de direcciones, y la configuración que ayer funcionaba hoy devuelve 407. Compara tu IP actual con la dirección guardada en el panel. Si tu dirección cambia a menudo, usuario y contraseña es el método con menos sorpresas; nuestra guía de autenticación de proxy compara los dos.

Parámetros de sesión. Los parámetros que se añaden al usuario para sesiones sticky o para elegir país, como user-session-abc-country-tr, forman parte del usuario; una errata o un parámetro no admitido vuelve como 407 en algunos proveedores. Prueba primero con un user:pass simple y vuelve a añadir los parámetros uno a uno. En un plan de Proxies residenciales fijan la duración de la sesión y el país de salida, así que separar un error de parámetro de un error de contraseña ahorra tiempo.

¿Cómo solucionar un 407 en un proxy corporativo?

En la red de una empresa o una universidad no configuraste tú el proxy; el sistema operativo lo toma de un archivo de configuración automática (PAC) o de una directiva de grupo, y las credenciales suelen ser tu cuenta de dominio. Estos proxies piden a menudo Proxy-Authenticate: Negotiate (Kerberos) o NTLM en lugar de Basic. Los navegadores y Office usan la cuenta de Windows con la que has iniciado sesión y pasan en silencio; las herramientas de línea de comandos y los scripts no, y devuelven 407.

cURL te deja elegir el esquema: --proxy-negotiate -U : usa la identidad de la sesión para Kerberos, y --proxy-anyauth elige un método adecuado entre los que ofrece el proxy. En nuestra prueba, con --proxy-anyauth cURL envió primero una petición sin autenticar, leyó el 407 y su Proxy-Authenticate: Basic y luego repitió con Basic; un viaje de ida y vuelta más, pero útil cuando no sabes qué espera el proxy. NTLM es otro asunto. Desde la versión 8.20.0 (29 de abril de 2026), el proyecto curl desactiva NTLM por defecto en la compilación y planea eliminarlo por completo, alegando la criptografía débil del esquema, su incompatibilidad con HTTP/2 y HTTP/3 y la propia retirada de NTLM por parte de Microsoft. Con cURL 8.20 o posterior, --proxy-ntlm ya no funciona en la mayoría de las distribuciones; si el proxy solo acepta NTLM, pide al equipo de red que active Negotiate. Nuestro cURL 8.5 todavía envió el primer mensaje de NTLM con --proxy-ntlm; nuestro proxy de prueba, que solo admite Basic, respondió 407 de nuevo.

La ventana que no deja de reabrirse en Windows. La configuración de proxy de Windows guarda solo la dirección, no un usuario y una contraseña; cada aplicación pide las credenciales por su cuenta cuando recibe un 407. Si la ventana se cierra y se reabre al instante, las credenciales fueron rechazadas: tras un cambio de contraseña reciente, el Administrador de credenciales de Windows sigue probando la contraseña antigua guardada hasta que la borras. Una nota de soporte antigua de Microsoft describe otra causa: cuando varios proxies que exigen NTLM forman una cadena, el primero se pasa, el segundo devuelve 407 y la ventana reaparece; la solución sugerida es activar Negotiate con Kerberos, que es trabajo del equipo de red.

¿Por qué Chrome y Puppeteer muestran el 407 de otra forma?

Chrome no usa un usuario y una contraseña escritos en la dirección del proxy. La documentación de proxy de Chromium lo dice sin rodeos: el navegador no implementa credenciales incrustadas en la configuración del proxy, y la autenticación de proxy pasa por el flujo ordinario, es decir, la ventana que sigue a un 407. Los proxies HTTP admiten Basic, Digest, Negotiate y NTLM; SOCKS5 no admite ninguna autenticación. Escribir user:pass@ en --proxy-server no sirve, por tanto, de nada. En la prueba para nuestro artículo de Puppeteer del 6 de octubre, Chrome 154 rechazó el valor completo con net::ERR_NO_SUPPORTED_PROXIES; sin dar las credenciales en ningún sitio, la primera navegación falló con net::ERR_INVALID_AUTH_CREDENTIALS. El 407 nunca llega al usuario en esta versión; Chrome lo convierte en su propio código de error.

La solución es poner las credenciales donde se construye la respuesta del navegador al 407. En Puppeteer eso es page.authenticate(), y tiene que ir antes del primer page.goto():

js
import puppeteer from "puppeteer";

const browser = await puppeteer.launch({
  args: ["--proxy-server=http://pr.proxynet.io:8000"], // sin credenciales en la dirección
});
const page = await browser.newPage();
await page.authenticate({ username: "user", password: "pass" }); // la respuesta al 407
await page.goto("https://httpbin.org/ip");

Según la documentación de Puppeteer, la llamada activa la interceptación de peticiones entre bastidores y se desactiva pasando null. Las credenciales pertenecen al contexto del navegador, no a la página; todas las páginas del mismo contexto usan el primer valor introducido. En un Chrome que manejas a mano, las escribes en la ventana que se abre tras el 407.

¿Cómo se pasan las credenciales del proxy en cada herramienta?

Cada herramienta recibe las credenciales en un lugar distinto. Todos los ejemplos pasaron por el proxy de prueba local; la dirección se muestra como pr.proxynet.io:8000 y las credenciales como user:pass.

cURL. Las credenciales van en la dirección o en una opción aparte; -U y --proxy-user son lo mismo. cURL usa Basic por defecto y envía las credenciales en la primera petición.

bash
# credenciales en una opción aparte: no hace falta codificar los caracteres especiales
curl -x http://pr.proxynet.io:8000 -U user:pass https://httpbin.org/ip

# credenciales en la dirección: %40 para @, %3A para :
curl -x http://user:p%40ss@pr.proxynet.io:8000 https://httpbin.org/ip

# ver qué esquema espera el proxy
curl -v -x http://pr.proxynet.io:8000 https://httpbin.org/ip 2>&1 | grep -i proxy-auth

Las variables de entorno aceptan la misma dirección: export HTTPS_PROXY=http://user:pass@pr.proxynet.io:8000, con HTTP_PROXY para HTTP simple y NO_PROXY para los hosts que se omiten. El resto de opciones de proxy de cURL está en nuestra guía de cURL con proxy.

Python Requests. Las credenciales van en la dirección del diccionario proxies o en HTTPProxyAuth, que añade la cabecera Proxy-Authorization. La documentación de Requests define la forma http://user:password@host/ y advierte de que guardar usuario y contraseña en una variable de entorno o en un archivo bajo control de versiones es un riesgo de seguridad.

python
import requests
from requests.auth import HTTPProxyAuth

PROXY = "http://user:pass@pr.proxynet.io:8000"
proxies = {"http": PROXY, "https": PROXY}

r = requests.get("https://httpbin.org/ip", proxies=proxies, timeout=15)
print(r.status_code, r.json())

# credenciales separadas de la dirección: no hace falta codificar los caracteres especiales
auth = HTTPProxyAuth("user", "pass")
r = requests.get("https://httpbin.org/ip", proxies={"https": "http://pr.proxynet.io:8000"}, auth=auth, timeout=15)

Los dos caminos pasaron el proxy en nuestra prueba con Requests 2.34. La forma del error depende del destino: para una dirección HTTPS, una petición sin autenticar lanzó requests.exceptions.ProxyError con Tunnel connection failed: 407 Proxy Authentication Required en el mensaje; para una dirección HTTP simple no hubo excepción, r.status_code fue 407 y r.headers["Proxy-Authenticate"] llevaba el desafío. Un script que solo captura excepciones se pierde el 407 en direcciones HTTP; necesita una comprobación r.status_code == 407. HTTPProxyAuth añade solo la cabecera Proxy-Authorization; el inicio de sesión propio del sitio web pasa por HTTPBasicAuth. Para enviar una serie de peticiones POST por una misma sesión, añade proxies a la configuración de Session de nuestra guía de POST y JSON con Requests.

Node.js y undici. El fetch integrado de Node pasa por un proxy con la clase ProxyAgent de undici. Las credenciales van en uri o en la opción token como valor de cabecera ya preparado; la documentación de ProxyAgent de undici describe ambos y señala que las credenciales de la URI se decodifican como URL antes de codificarse en Base64.

js
import { fetch, ProxyAgent } from "undici";

const agent = new ProxyAgent({
  uri: "http://pr.proxynet.io:8000",
  token: `Basic ${Buffer.from("user:pass").toString("base64")}`, // el valor de Proxy-Authorization
});

const res = await fetch("https://httpbin.org/ip", { dispatcher: agent });
console.log(res.status, await res.json());

Con undici 8.10, token y una uri con user:p%40ss dieron el mismo resultado. Una petición sin autenticar falló con TypeError: fetch failed; el motivo real estaba en error.cause como InvalidArgumentError: Proxy Authentication Required (407), y para una dirección HTTPS decía solo Request was cancelled. Node tampoco lee por sí mismo las variables de entorno de proxy: con HTTPS_PROXY definida, fetch siguió conectando en directo. La variable NODE_USE_ENV_PROXY=1, introducida en Node 22.21 y 24.0, lo activa; en nuestra prueba funcionó con un aviso de característica experimental.

npm, pip, Git y Docker. Los cuatro aceptan el mismo formato de dirección, con las credenciales dentro de la dirección.

bash
# npm: ajuste permanente (la variable de entorno HTTPS_PROXY también se lee)
npm config set proxy http://user:pass@pr.proxynet.io:8000
npm config set https-proxy http://user:pass@pr.proxynet.io:8000

# pip: --proxy para un solo comando, proxy = en pip.conf de forma permanente
pip install --proxy http://user:pass@pr.proxynet.io:8000 requests

# Git: formato [protocol://][user[:password]@]proxyhost[:port]
git config --global http.proxy http://user:pass@pr.proxynet.io:8000
git config --global http.proxyAuthMethod basic   # anyauth, basic, digest, negotiate, ntlm

Para el cliente de Docker, el ajuste va en el objeto proxies.default (httpProxy, httpsProxy, noProxy) de ~/.docker/config.json; la documentación de Docker señala que estos valores se guardan en texto plano en la configuración del contenedor y pueden leerse mediante la API remota. En nuestra prueba, Git dijo fatal: unable to access '…': The requested URL returned error: 407 sin credenciales y pasó con ellas. pip escondió el 407 en su salida por defecto tras Could not find a version that satisfies the requirement; la causa real apareció con pip -vvv: 407 Client Error: Proxy Authentication Required.

HerramientaCómo muestra un 407Dónde van las credenciales
cURLCONNECT tunnel failed, response 407-U user:pass o la dirección
Python RequestsProxyError en HTTPS, status_code == 407 en HTTPDirección en proxies= o HTTPProxyAuth
Node undicifetch failed, el motivo en causeProxyAgent token o uri
Chrome / PuppeteerERR_INVALID_AUTH_CREDENTIALS, una ventanapage.authenticate()
GitThe requested URL returned error: 407Dirección en http.proxy
pipSolo con -vvv--proxy o pip.conf

¿Cómo evita una IP estática los 407?

Con autenticación por IP, la causa más frecuente de un 407 es una dirección que cambió. Desde una IP pública fija, como un servidor en la nube o una línea de oficina, rellenas la lista una vez y no llevas contraseña en tu código; desde una conexión doméstica o redes cambiantes, usuario y contraseña causa menos interrupciones.

La estabilidad también importa del lado del proxy. En un plan de Proxies ISP, la IP de salida no cambia, así que una sola dirección entra en la lista de permitidos del sitio web o de un sistema corporativo y en ese extremo tampoco hay problemas de credenciales.

Errores habituales

  • Buscar el error en el sitio web. Una petición con 407 nunca llegó al sitio; su cuenta, sus cookies o su clave de API no cambian el resultado.
  • Escribir una contraseña sin codificar en la URL. Una contraseña con @ rompe la dirección; usa %40 o una opción de credenciales aparte.
  • Poner user:pass@ en el flag de Chrome. Chromium lo ignora; usa page.authenticate() en Puppeteer y la ventana a mano.
  • Capturar solo excepciones. Requests no lanza nada para una dirección HTTP simple y devuelve un objeto de respuesta con 407; comprueba el código de estado.
  • Fiarse de la IP antigua. Con autenticación por IP, la configuración de ayer puede no coincidir con la dirección de hoy.
  • Escribir credenciales en scripts o repositorios. Tanto la documentación de Requests como la de Docker lo llaman un riesgo; usa una variable de entorno limitada a la sesión o un gestor de secretos.
  • Esperar NTLM después de cURL 8.20. NTLM está desactivado en la compilación; pide Negotiate (Kerberos) en los proxies corporativos.

Guía de decisión

Tu situaciónQué hacer
Sin Proxy-Authorization en curl -vAñade las credenciales con -U o en la dirección
Cabecera presente, sigue el 407Vuelve a copiar la contraseña del panel; prueba un user:pass simple
@, : o # en la contraseña%40, %3A, %23 en la URL, o una opción de credenciales aparte
Autenticación por IP, cambió la direcciónNueva IP en el panel; pasa a usuario y contraseña si cambia a menudo
Chrome o Puppeteerpage.authenticate(); sin credenciales en el flag
Red de empresa, Proxy-Authenticate: Negotiate--proxy-negotiate -U : o la cuenta de dominio; Kerberos en lugar de NTLM
La ventana no desaparece en WindowsBorra la contraseña antigua en el Administrador de credenciales; pregunta al equipo de red por proxies encadenados

Preguntas frecuentes

¿Qué significa el error HTTP 407?

Significa que el proxy quiere credenciales antes de reenviar tu petición, y que la petición no llevaba ninguna o llevaba unas erróneas. La cabecera Proxy-Authenticate de la respuesta nombra el esquema esperado; la solución es añadir una cabecera Proxy-Authorization con el valor correcto.

¿Qué diferencia hay entre 407 y 401?

En un 401, el sitio web pide credenciales mediante WWW-Authenticate y Authorization; en un 407 las pide el proxy intermedio mediante Proxy-Authenticate y Proxy-Authorization. Una petición que recibió un 407 nunca llegó al sitio.

¿Por qué recibo 407 si la contraseña de mi proxy es correcta?

Tres causas frecuentes: un carácter especial de la contraseña no está codificado en la URL, la herramienta no lee las credenciales de la dirección (Chrome funciona así) o la cuenta está en modo de autenticación por IP y tu dirección actual no está en la lista. Comprueba con curl -v si se envía siquiera una línea Proxy-Authorization.

¿Cómo escribo una contraseña con el signo @ en la dirección del proxy?

Sustituye @ por %40: http://user:p%40ss@host:port. Con la misma lógica, usa %3A para : y %23 para #. Las opciones aparte, como -U "user:p@ss" en cURL o HTTPProxyAuth("user", "p@ss") en Requests, no necesitan codificación.

¿Dónde introduzco el usuario y la contraseña del proxy en Chrome?

La configuración de proxy de Chrome no tiene campo de usuario, y el navegador ignora la parte user:pass@ de una dirección. Escribes las credenciales en la ventana que se abre tras el primer 407; en herramientas de automatización como Puppeteer, page.authenticate({ username, password }) sustituye a esa ventana.

Recibo 407 en el proxy de mi empresa. ¿Qué hago?

Lo más probable es que el proxy quiera NTLM o Kerberos (Negotiate) y espere tu cuenta de dominio. Prueba --proxy-negotiate -U : en cURL; como NTLM está desactivado en las compilaciones de cURL desde la 8.20, pide al equipo de red que active Negotiate para un proxy que solo acepta NTLM.

En resumen

HTTP 407 Proxy Authentication Required dice que el proxy que reenvía tu petición quiere credenciales; el sitio web no sabe nada de ello. La solución es la misma cabecera con el valor correcto en cada herramienta: -U en cURL, la dirección en proxies= o HTTPProxyAuth en Requests, token en undici, page.authenticate() en Puppeteer y la dirección http://user:pass@host:port en npm, pip, Git y Docker. Una @ en la contraseña se convierte en %40, la autenticación por IP necesita tu dirección actual en la lista, y las redes corporativas hablan Negotiate en vez de Basic, con NTLM desactivado en las compilaciones de cURL desde la 8.20. Para planes que funcionan con usuario y contraseña o con una lista de IP, consulta nuestros servicios de proxy.