Solución de problemas9 min de lectura

Corrige ERR_TUNNEL_CONNECTION_FAILED en Playwright y Puppeteer

ERR_TUNNEL_CONNECTION_FAILED y ERR_PROXY_CONNECTION_FAILED explicados con una matriz verificada de fallos de proxy y cómo recuperar el estado CONNECT que Chromium oculta.

En esta página

net::ERR_TUNNEL_CONNECTION_FAILED significa que Chromium llegó a tu proxy, le pidió abrir un túnel hacia un destino HTTPS con una petición CONNECT y el proxy respondió con algo distinto de un éxito. net::ERR_PROXY_CONNECTION_FAILED significa que Chromium nunca llegó tan lejos: no pudo establecer ninguna conexión con el proxy. Ambos códigos provienen de la pila de red de Chromium, así que Playwright, Puppeteer y cualquier otra herramienta que controle Chromium reportan las mismas cadenas, normalmente como page.goto: net::ERR_TUNNEL_CONNECTION_FAILED at https://....

Lo frustrante es lo que Chromium omite. Un proxy que rechaza un túnel con 403, uno que te está limitando con 429 y uno cuyo upstream está caído con 502 producen exactamente el mismo ERR_TUNNEL_CONNECTION_FAILED. Esta guía muestra qué comportamiento del proxy produce qué error en Playwright, Puppeteer y curl, reproducido contra pequeños proxies controlados y no recopilado de hilos de foros, y después te da un procedimiento corto para recuperar el estado que el navegador oculta.

¿Lo ves en Chrome o Edge sin ningún código?

El significado es el mismo en una ventana normal del navegador. Chrome y Edge comparten la pila de red de Chromium y, por defecto, toman su configuración de proxy del sistema operativo, así que el error te dice que hay un proxy configurado en algún sitio y que rechazó abrir el túnel (ERR_TUNNEL_CONNECTION_FAILED) o que no se pudo alcanzar en absoluto (ERR_PROXY_CONNECTION_FAILED). El sitio web en sí no está caído; el navegador nunca pasó del proxy. Las fuentes habituales de esa configuración son un cliente VPN que instala un proxy del sistema mientras está conectado, una extensión de proxy o VPN del navegador, una política de empresa o un script PAC en un dispositivo gestionado, o un proxy que se introdujo a mano y se olvidó.

Cómo solucionarlo, en el orden que encuentra la causa más rápido:

  • Abre chrome://net-internals/#proxy (o edge://net-internals/#proxy) para ver la configuración de proxy efectiva que el navegador está usando ahora mismo. Si no esperabas un proxy ahí, ese es el problema.
  • Desconecta la VPN o desactiva las extensiones de proxy, recarga y compara. Si la página carga, el proxy de la VPN o de la extensión estaba rechazando el túnel o no era alcanzable.
  • En un dispositivo de trabajo o de estudios, las reglas del administrador del proxy deciden qué sitios se permiten. Un ERR_TUNNEL_CONNECTION_FAILED en un sitio HTTPS bloqueado por política es el comportamiento esperado, y solo el administrador puede cambiarlo.
  • Si las páginas http:// cargan pero las https:// fallan, el proxy está rechazando el CONNECT al puerto 443. Ese es el caso de túnel de la matriz de abajo, y el propio código de estado del proxy dice por qué.

El resto de esta guía está escrito para desarrolladores que configuraron el proxy ellos mismos en Playwright o Puppeteer, pero cada cadena de error significa exactamente lo mismo en la barra de direcciones.

Dos errores, dos etapas

Para un destino HTTPS a través de un proxy HTTP, Chromium resuelve el nombre de host del proxy, abre una conexión TCP con él, envía CONNECT host:443, espera una respuesta 2xx y solo entonces inicia el handshake TLS del destino dentro del túnel. Los dos códigos de error marcan dos puntos distintos de esa ruta:

  • ERR_PROXY_CONNECTION_FAILED: el nombre de host del proxy no se resolvió, nada aceptó la conexión TCP, o la conexión con el proxy falló antes de intercambiar ningún HTTP. Una variante autoinfligida habitual es escribir https:// delante de un gateway que solo habla HTTP plano, lo que hace que Chromium intente un handshake TLS con un servidor que espera texto en claro.
  • ERR_TUNNEL_CONNECTION_FAILED: la conexión TCP funcionó y el proxy respondió al CONNECT, pero no con un estado de éxito. Chromium descarta el código de estado, la frase de motivo y cualquier cuerpo que el proxy haya enviado.

Todo lo demás que puedas ver (ERR_CONNECTION_RESET, ERR_INVALID_HTTP_RESPONSE, ERR_SOCKS_CONNECTION_FAILED, un simple timeout de navegación) pertenece a una etapa distinta y se trata más abajo.

Qué aspecto tiene cada fallo del proxy

La tabla registra lo que reportó cada cliente ante un comportamiento deliberado del proxy. Playwright usó su opción de lanzamiento proxy; Puppeteer usó flags de Chromium en bruto (--proxy-server) con page.authenticate para las credenciales; curl usó -x con --proxy-user. Los tres hablaron con la misma build de Chromium 153 o con curl 8.18 en Linux; los detalles están en la sección de método.

Comportamiento del proxyPlaywright 1.63 (Chromium 153)Puppeteer 25 (mismo Chromium)curl 8.18
El nombre de host del proxy no se resuelveERR_PROXY_CONNECTION_FAILEDigual(5) Could not resolve proxy
Nada escucha en el puerto del proxyERR_PROXY_CONNECTION_FAILEDigual(7) Failed to connect
Esquema https://, pero el proxy habla HTTP planoERR_PROXY_CONNECTION_FAILEDigual(35) TLS connect error
El proxy acepta TCP y nunca respondetimeout de navegacióntimeout de navegación(28) Connection timed out
El proxy acepta TCP y cierra de inmediatoERR_CONNECTION_RESETigual(56) Proxy CONNECT aborted o (56) Recv failure: Connection reset by peer
El puerto responde, pero no con HTTPERR_INVALID_HTTP_RESPONSEigual(56) Proxy CONNECT aborted
CONNECT respondió 403ERR_TUNNEL_CONNECTION_FAILEDigual(7) CONNECT tunnel failed, response 403
CONNECT respondió 429ERR_TUNNEL_CONNECTION_FAILEDigual(7) CONNECT tunnel failed, response 429
CONNECT respondió 502 o 503ERR_TUNNEL_CONNECTION_FAILEDigual(7) CONNECT tunnel failed, response 502 o 503
CONNECT respondió 407, sin credenciales configuradasla navegación se resuelve con estado 407 y después ERR_TUNNEL_CONNECTION_FAILED en la peticiónERR_INVALID_AUTH_CREDENTIALS(7) CONNECT tunnel failed, response 407
CONNECT respondió 407, credenciales incorrectasla navegación se resuelve con estado 407 y después ERR_TUNNEL_CONNECTION_FAILED en la peticiónla navegación se resuelve con estado 407 y la página de error de Chromium, y después ERR_HTTP_RESPONSE_CODE_FAILURE(7) CONNECT tunnel failed, response 407
Credenciales correctas200 del destino200200, http_connect 200
CONNECT respondió 200 y después el proxy corta el túnelERR_CONNECTION_CLOSED o ERR_CONNECTION_RESETERR_CONNECTION_RESET(35) Send failure: Broken pipe
Esquema socks5:// contra un proxy HTTPERR_SOCKS_CONNECTION_FAILEDigual(97) Received invalid version in initial SOCKS5 response
socks5:// con usuario y contraseñarechazado al lanzar: Browser does not support socks5 proxy authenticationno probadono probado
Destino http:// plano, el proxy rechaza con 403 o 407sin error: la propia página 403 o 407 del proxy es la respuesta de la navegaciónigual403 o 407 con http_connect 000

Tres filas merecen una segunda mirada. Todo estado CONNECT distinto de éxito, del 403 al 503, se reduce a un único error del navegador, así que el navegador por sí solo no puede decirte si estás bloqueado, limitado o ante un upstream roto. El 407 es el único estado que Chromium trata de forma especial, y Playwright y Puppeteer lo exponen de forma distinta. Y un destino http:// plano nunca produce un error de túnel, porque no hay túnel: el rechazo del proxy llega como una respuesta normal con el estado y el cuerpo del proxy, y por eso el mismo script puede «funcionar» en una página de prueba HTTP y fallar en el sitio HTTPS que te importa.

Recupera el estado del CONNECT con curl

curl conserva el estado que Chromium descarta. Ejecuta una petición acotada contra el mismo gateway, destino y credenciales que usa tu script del navegador, e imprime tanto el estado del destino como el estado del CONNECT. El comando lee PROXY_URL, PROXY_USERNAME y PROXY_PASSWORD del entorno, de modo que la contraseña nunca aparece en un argumento del shell ni en el historial; se requiere curl 8.3 o posterior para la expansión de variables.

sh
: "${PROXY_URL:?Set the gateway as http://host:port (no credentials in the URL)}"
CHECK_URL=${CHECK_URL:-https://example.com/}
curl --disable --silent --show-error --output /dev/null \
  --noproxy '' --proxy "$PROXY_URL" --proxy-basic \
  --variable %PROXY_USERNAME --variable %PROXY_PASSWORD \
  --expand-proxy-user '{{PROXY_USERNAME}}:{{PROXY_PASSWORD}}' \
  --max-time 15 \
  --write-out 'destination=%{http_code} connect=%{http_connect} exit=%{exitcode}\n' \
  "$CHECK_URL"

connect=200 con un estado del destino significa que el túnel funciona y que el problema del navegador está en otra parte. Un valor de connect distinto de cero es el estado que Chromium ocultó:

  • 407: el proxy quiere credenciales que no aceptó. Revisa la cuenta o el subusuario, el percent-encoding y si este gateway autentica por contraseña o por IP de origen autorizada. La guía del 407 recorre ese orden.
  • 403: el proxy aceptó la conexión pero rechaza este túnel. Las causas típicas son un destino o puerto fuera de la lista permitida del proveedor, una restricción de la cuenta, o un parámetro de país o sesión que el gateway rechaza.
  • 429: límites de concurrencia o de peticiones en el proxy. Busca una cabecera Retry-After en la salida de curl -v y reduce los navegadores en paralelo antes de reintentar.
  • 502, 503, 504: el proxy no pudo alcanzar ni seleccionar una salida upstream. Reintentar de inmediato rara vez ayuda; anota la hora y pregunta al proveedor qué registró su gateway.

connect=000 con un código de salida de curl distinto de 0 significa que el fallo ocurrió antes de cualquier respuesta al CONNECT, lo que coincide con las filas de ERR_PROXY_CONNECTION_FAILED y de reset de arriba. La guía de timeouts muestra cómo cronometrar cada etapa cuando el fallo es lento en vez de inmediato.

Playwright: un 407 no lanza excepción

Sin credenciales de proxy, o con credenciales incorrectas, page.goto no rechazó la promesa en la ejecución registrada. Se resolvió con una respuesta cuyo estado era 407 y cuyo statusText era Proxy Authentication Required, con la cabecera Proxy-Authenticate del proxy legible en esa respuesta y un cuerpo vacío; para la misma navegación se disparó un evento requestfailed con ERR_TUNNEL_CONNECTION_FAILED. Un script que solo espera a goto y continúa, por tanto, sigue ejecutándose contra una página vacía. Comprueba response.ok() y trata un 407 como un problema del proxy, no del destino:

js
import { chromium } from 'playwright';

function required(name) {
  const value = process.env[name];
  if (!value) throw new Error('Missing environment variable: ' + name);
  return value;
}

const browser = await chromium.launch({
  proxy: {
    server: required('PROXY_URL'),           // http://host:port, no credentials in the URL
    username: required('PROXY_USERNAME'),
    password: required('PROXY_PASSWORD'),
  },
});
try {
  const page = await browser.newPage();
  page.on('requestfailed', (request) => {
    console.error('request failed:', request.failure()?.errorText, request.url());
  });
  const response = await page.goto('https://example.com/', { waitUntil: 'domcontentloaded' });
  if (!response) throw new Error('No navigation response');
  if (response.status() === 407) throw new Error('Proxy rejected the credentials (407)');
  if (!response.ok()) throw new Error('Destination answered ' + response.status());
} finally {
  await browser.close();
}

Pon las credenciales del proxy en proxy.username y proxy.password. En la ejecución registrada, las credenciales proporcionadas mediante la opción httpCredentials del contexto también satisficieron el desafío del proxy, porque Playwright responde a la petición de autenticación de Chromium desde cualquiera de las dos fuentes. No confíes en eso: httpCredentials está pensado para el destino, y reutilizarlo para el proxy envía las credenciales de un sitio al operador del proxy.

Dos detalles más de Playwright de la ejecución. Playwright añade <-loopback> a la lista de exclusión por sí mismo, así que un destino en localhost o 127.0.0.1 pasa por el proxy; Chromium en bruto hace lo contrario (mira la sección de Puppeteer). Y un servidor socks5:// con usuario y contraseña se rechaza antes de que arranque el navegador, con Browser does not support socks5 proxy authentication. Chromium no tiene soporte de autenticación SOCKS5, así que la solución es un gateway HTTP con autenticación Basic o una lista de IP permitidas, no otro ajuste de SOCKS. La guía HTTP vs SOCKS5 explica qué cambia entre los dos.

Puppeteer: autentica antes de la primera navegación

Puppeteer pasa el proxy a Chromium como un flag y responde a los desafíos 407 solo después de que se haya llamado a page.authenticate. Sin esa llamada, la ejecución registrada produjo net::ERR_INVALID_AUTH_CREDENTIALS, un código que mucha gente nunca relaciona con un proxy. Con credenciales incorrectas, goto se resolvió con estado 407 y la página de error integrada de Chromium como cuerpo, además de un evento requestfailed con ERR_HTTP_RESPONSE_CODE_FAILURE.

js
import puppeteer from 'puppeteer';

function required(name) {
  const value = process.env[name];
  if (!value) throw new Error('Missing environment variable: ' + name);
  return value;
}

const browser = await puppeteer.launch({
  args: [
    '--proxy-server=' + required('PROXY_URL'),   // http://host:port
    '--proxy-bypass-list=<-loopback>',          // only if local destinations must use the proxy
  ],
});
try {
  const page = await browser.newPage();
  await page.authenticate({
    username: required('PROXY_USERNAME'),
    password: required('PROXY_PASSWORD'),
  });
  page.on('requestfailed', (request) => {
    console.error('request failed:', request.failure()?.errorText, request.url());
  });
  const response = await page.goto('https://example.com/', { waitUntil: 'domcontentloaded' });
  if (!response) throw new Error('No navigation response');
  if (response.status() === 407) throw new Error('Proxy rejected the credentials (407)');
  if (!response.ok()) throw new Error('Destination answered ' + response.status());
} finally {
  await browser.close();
}

Aquí importan dos valores predeterminados de Chromium. Primero, Chromium se salta el proxy para localhost, 127.0.0.1 y [::1] a menos que la lista de exclusión contenga <-loopback>. En la ejecución, un script de Puppeteer apuntado a un proxy que rechazaba todos los túneles cargó igualmente una página HTTPS local con estado 200, porque nunca se consultó al proxy. Una prueba que «pasa» contra un servidor local no demuestra nada sobre el proxy. Segundo, un Chromium en bruto lanzado con --proxy-server también enrutó su propio tráfico en segundo plano a través del proxy: el registro del proxy grabado muestra un GET a clients2.google.com y un CONNECT a update.googleapis.com:443 antes de la navegación de la página. Espera esos nombres de host en los registros del proveedor y en el tráfico medido, y no confundas sus fallos con un fallo de tu navegación. Los flags de lanzamiento de Playwright no produjeron ese tráfico en la misma ejecución.

Errores que no son errores de túnel

  • ERR_CONNECTION_RESET o ERR_CONNECTION_CLOSED justo después de conectar: el proxy aceptó la conexión TCP y la cortó, ya sea antes de responder al CONNECT o inmediatamente después de un 200. Los proveedores hacen esto cuando una salida no está disponible o cuando la conexión se está limitando a nivel de socket. curl reporta Proxy CONNECT aborted o Send failure: Broken pipe para el mismo comportamiento.
  • ERR_INVALID_HTTP_RESPONSE: algo respondió en ese puerto, pero no en HTTP. Normalmente el número de puerto pertenece a un listener SOCKS, a un listener solo TLS o a otro servicio.
  • ERR_SOCKS_CONNECTION_FAILED: configuraste socks5:// y el servidor habló HTTP. Cambia el esquema para que coincida con el protocolo documentado del gateway antes de tocar nada más.
  • Un timeout de navegación sin error net: el proxy aceptó la conexión y nunca respondió. Subir el timeout no ayudará; verifica el puerto y el protocolo con curl, que agota el tiempo con (28) en la misma situación.
  • ERR_PROXY_CONNECTION_FAILED en un gateway que funciona en curl: compara el esquema. Chromium intenta TLS con los proxies https://, y la mayoría de los gateways son endpoints http:// planos que transportan destinos HTTPS dentro del túnel.

Un orden de diagnóstico

  1. Reproduce con curl usando el mismo gateway, destino y credenciales, y lee http_connect. Esto convierte el error de una palabra del navegador en el estado real del proxy en menos de un minuto.
  2. Confirma el esquema del proxy (http://, https:// o socks5://) contra las instrucciones actuales del proveedor. Dos de las tres filas de ERR_PROXY_CONNECTION_FAILED de arriba son errores de esquema.
  3. Pon las credenciales donde la herramienta las espera: proxy.username y proxy.password en Playwright, page.authenticate antes de la primera navegación en Puppeteer. Comprueba response.status() === 407 explícitamente en ambos.
  4. Prueba con un destino HTTPS, no HTTP. Los destinos HTTP se saltan el CONNECT y esconden los problemas de túnel detrás de respuestas normales del proxy.
  5. Revisa las reglas de exclusión. Los destinos locales e internos se saltan el proxy en Chromium en bruto, y la configuración de proxy corporativa o del entorno puede sobrescribir lo que pasaste.
  6. Informa con las versiones de los clientes, la hora en UTC, el host y el puerto del gateway, el nombre de host del destino y el valor http_connect de curl. Deja fuera las contraseñas, las URL con credenciales y las trazas en bruto; la guía del 407 tiene una plantilla de informe sin datos sensibles.

Método y descargas

ipvolt ejecutó la reproducción el 17 de septiembre de 2026 en un host Linux con Node.js 24.20.0, Playwright 1.63.0 (Chromium 153.0.8010.12, headless), puppeteer-core 25.11.0 controlando la misma build de Chromium y curl 8.18.0. Cada «proxy» era un pequeño servidor Node.js en 127.0.0.1 con exactamente un comportamiento; el destino era un servidor HTTPS local con un certificado autofirmado desechable, así que los casos de navegador ignoraron los errores de certificado del destino, lo que no afecta a cómo Chromium habla con el proxy. Firefox y WebKit no se probaron, y no intervino ningún gateway comercial: la tabla describe la reacción de Chromium ante un comportamiento del proxy, no la política de ningún proveedor.

El archivo del laboratorio contiene el script del laboratorio, el manifiesto del paquete, el README y los resultados registrados, incluido el registro del lado del proxy que muestra si cada cliente llegó al proxy y si las credenciales llegaron. Se ejecuta solo en loopback y no necesita ninguna cuenta de proxy.

Si quieres enterarte de cuándo se abre el acceso a ipvolt, únete a la lista de acceso anticipado. Un correo cuando se abra el acceso. Nada más. Estos ejemplos son diagnósticos del cliente, no documentación de un endpoint de ipvolt disponible.

Fuentes y lecturas adicionales

Referencias técnicas usadas para esta guía. Consulta la documentación de tu versión instalada y la configuración compatible de tu proveedor.