# Corrige ERR_TUNNEL_CONNECTION_FAILED en Playwright y Puppeteer

Source: https://ipvolt.com/es/guides/fix-err-tunnel-connection-failed
Markdown: https://ipvolt.com/es/guides/fix-err-tunnel-connection-failed.md
Language: es

[Inicio de ipvolt](https://ipvolt.com/es.md) / [Guías](https://ipvolt.com/es/guides.md) / Corrige ERR_TUNNEL_CONNECTION_FAILED en Playwright y Puppeteer

Solución de problemas
Revisado: 2026-09-17
Publicado: 2026-09-17
9 min de lectura
Por ipvolt

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.

`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 proxy | Playwright 1.63 (Chromium 153) | Puppeteer 25 (mismo Chromium) | curl 8.18 |
| --- | --- | --- | --- |
| El nombre de host del proxy no se resuelve | `ERR_PROXY_CONNECTION_FAILED` | igual | `(5) Could not resolve proxy` |
| Nada escucha en el puerto del proxy | `ERR_PROXY_CONNECTION_FAILED` | igual | `(7) Failed to connect` |
| Esquema `https://`, pero el proxy habla HTTP plano | `ERR_PROXY_CONNECTION_FAILED` | igual | `(35) TLS connect error` |
| El proxy acepta TCP y nunca responde | timeout de navegación | timeout de navegación | `(28) Connection timed out` |
| El proxy acepta TCP y cierra de inmediato | `ERR_CONNECTION_RESET` | igual | `(56) Proxy CONNECT aborted` o `(56) Recv failure: Connection reset by peer` |
| El puerto responde, pero no con HTTP | `ERR_INVALID_HTTP_RESPONSE` | igual | `(56) Proxy CONNECT aborted` |
| CONNECT respondió 403 | `ERR_TUNNEL_CONNECTION_FAILED` | igual | `(7) CONNECT tunnel failed, response 403` |
| CONNECT respondió 429 | `ERR_TUNNEL_CONNECTION_FAILED` | igual | `(7) CONNECT tunnel failed, response 429` |
| CONNECT respondió 502 o 503 | `ERR_TUNNEL_CONNECTION_FAILED` | igual | `(7) CONNECT tunnel failed, response 502` o `503` |
| CONNECT respondió 407, sin credenciales configuradas | la navegación se resuelve con estado 407 y después `ERR_TUNNEL_CONNECTION_FAILED` en la petición | `ERR_INVALID_AUTH_CREDENTIALS` | `(7) CONNECT tunnel failed, response 407` |
| CONNECT respondió 407, credenciales incorrectas | la navegación se resuelve con estado 407 y después `ERR_TUNNEL_CONNECTION_FAILED` en la petición | la 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 correctas | 200 del destino | 200 | 200, `http_connect` 200 |
| CONNECT respondió 200 y después el proxy corta el túnel | `ERR_CONNECTION_CLOSED` o `ERR_CONNECTION_RESET` | `ERR_CONNECTION_RESET` | `(35) Send failure: Broken pipe` |
| Esquema `socks5://` contra un proxy HTTP | `ERR_SOCKS_CONNECTION_FAILED` | igual | `(97) Received invalid version in initial SOCKS5 response` |
| `socks5://` con usuario y contraseña | rechazado al lanzar: `Browser does not support socks5 proxy authentication` | no probado | no probado |
| Destino `http://` plano, el proxy rechaza con 403 o 407 | sin error: la propia página 403 o 407 del proxy es la respuesta de la navegación | igual | 403 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](/es/guides/fix-proxy-error-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](/es/guides/proxy-timeout-troubleshooting) 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](/es/guides/http-vs-socks5-proxies) 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](/es/guides/fix-proxy-error-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](https://ipvolt.com/downloads/chromium-proxy-tunnel-errors/chromium-proxy-tunnel-errors.zip) contiene el [script del laboratorio](https://ipvolt.com/downloads/chromium-proxy-tunnel-errors/tunnel-errors-lab.mjs), el [manifiesto del paquete](https://ipvolt.com/downloads/chromium-proxy-tunnel-errors/package.json), el [README](https://ipvolt.com/downloads/chromium-proxy-tunnel-errors/README.md) y los [resultados registrados](https://ipvolt.com/downloads/chromium-proxy-tunnel-errors/results.json), 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](https://ipvolt.com/#waitlist-closing). 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

- [Chromium network error list (net_error_list.h)](https://chromium.googlesource.com/chromium/src/+/main/net/base/net_error_list.h)
- [Chromium proxy support and bypass rules (proxy.md)](https://chromium.googlesource.com/chromium/src/+/main/net/docs/proxy.md)
- [Playwright: HTTP proxy configuration](https://playwright.dev/docs/network#http-proxy)
- [Puppeteer: page.authenticate](https://pptr.dev/api/puppeteer.page.authenticate)
- [curl: --write-out variables including http_connect](https://curl.se/docs/manpage.html#-w)
- [RFC 9110: the CONNECT method](https://www.rfc-editor.org/rfc/rfc9110.html#name-connect)
- [RFC 9110: 407 Proxy Authentication Required](https://www.rfc-editor.org/rfc/rfc9110.html#name-407-proxy-authentication-req)

## Guías relacionadas

- [Configurar un proxy en Playwright](https://ipvolt.com/es/guides/playwright-proxy-setup.md)
- [Corrige el error de proxy 407 sin adivinar](https://ipvolt.com/es/guides/fix-proxy-error-407.md)
- [Diagnostica los timeouts de proxy etapa por etapa](https://ipvolt.com/es/guides/proxy-timeout-troubleshooting.md)

## Sobre ipvolt

Los ejemplos usan configuraciones de proxy genéricas, con enlaces a la documentación técnica original. El comportamiento específico de cada producto debe comprobarse con tu proveedor. ipvolt sigue en desarrollo.

[Leer el original en inglés](https://ipvolt.com/guides/fix-err-tunnel-connection-failed.md)

## Entérate cuando se abra el acceso.

ipvolt · En desarrollo

Estamos construyendo infraestructura de proxies para desarrolladores y equipos de datos. Únete a la lista de interés para recibir un aviso cuando ipvolt esté listo.

Un correo cuando se abra el acceso. Nada más.

[Solicitar acceso anticipado](https://ipvolt.com/es/guides/fix-err-tunnel-connection-failed#waitlist-closing)

[Privacidad](https://ipvolt.com/privacy)

