# Tunnel connection failed: 502/503 con proxy: causas y soluciones

Source: https://ipvolt.com/es/guides/fix-proxy-tunnel-502-503
Markdown: https://ipvolt.com/es/guides/fix-proxy-tunnel-502-503.md
Language: es

[Inicio de ipvolt](https://ipvolt.com/es.md) / [Guías](https://ipvolt.com/es/guides.md) / Tunnel connection failed: 502/503 con proxy: causas y soluciones

Solución de problemas
Publicado: 2026-11-05
7 min de lectura
Por ipvolt

Un 502, 503 o 504 durante CONNECT viene del proxy; un 502 después de abrir el túnel viene del sitio. Cómo distinguirlos en cada cliente y qué corregir.

Un `502 Bad Gateway`, `503 Service Unavailable` o `504 Gateway Timeout` que llega mientras el cliente abre el túnel del proxy lo envía el proxy: el túnel nunca se abrió y el sitio web nunca vio tu petición. Un `502` que llega después de abrirse el túnel lo envía el sitio web, o algo que está delante de él. Mismo número, distinto responsable, distinta solución. Cada cadena de error y cada número de abajo provienen de un laboratorio en loopback ejecutado el 5 de noviembre de 2026, enlazado al final.

## Quién envió el 502

Para una URL `https://`, el cliente primero pide al proxy un túnel con `CONNECT host:443` ([RFC 9110](https://www.rfc-editor.org/rfc/rfc9110.html#name-connect)). Si el proxy responde algo distinto de 2xx, no hay túnel y la mayoría de los clientes lanzan una excepción. Si el túnel se abre y el sitio responde 502, recibes una respuesta normal.

| Cliente | El proxy rechazó el túnel | El sitio respondió 502 |
| --- | --- | --- |
| curl 8.5.0 | código de salida 56, `curl: (56) CONNECT tunnel failed, response 502` | código de salida 0, `%{http_code}` es 502, `%{http_connect}` es 200 |
| curl 8.18.0 | código de salida 7, `curl: (7) CONNECT tunnel failed, response 502` | código de salida 0, `%{http_code}` es 502, `%{http_connect}` es 200 |
| Requests 2.34 | `requests.exceptions.ProxyError` con `Tunnel connection failed: 502 Bad Gateway` | `Response` con `status_code == 502` |
| HTTPX 0.28 | `httpx.ProxyError: 502 Bad Gateway` | `Response` con `status_code == 502` |
| aiohttp 3.14 | `ClientHttpProxyError: 502, message='Bad Gateway'` | respuesta con `status == 502` |
| Playwright 1.63 | `page.goto: net::ERR_TUNNEL_CONNECTION_FAILED` | `page.goto` se resuelve, `status()` es 502 |
| Node.js 24 fetch | `TypeError: fetch failed`, y `err.cause.cause.message` es `Proxy response (502) !== 200 when HTTP Tunneling` | `response.status` es 502 |

503 y 504 siguen el mismo patrón con su propio número, por ejemplo `Tunnel connection failed: 503 Service Unavailable` o `curl: (7) CONNECT tunnel failed, response 504`. Chromium informó `net::ERR_TUNNEL_CONNECTION_FAILED` en los tres casos, sin el estado; la [guía de ERR_TUNNEL_CONNECTION_FAILED](/es/guides/fix-err-tunnel-connection-failed) cubre esa familia de errores.

Requests dice `Max retries exceeded` aunque por defecto no reintenta nada: el proxy del laboratorio recibió un solo CONNECT por petición. Node esconde el estado dos niveles más abajo, así que registra toda la cadena de `cause`.

Hay dos casos que rompen la regla. Para una URL `http://` simple no hay túnel, así que curl, Requests, HTTPX, aiohttp y Playwright devuelven el 502 del propio proxy como una respuesta; compara su cabecera `Server` y su cuerpo con los habituales del sitio. El fetch de Node también abre un túnel para las URL `http://`, así que ahí sigue siendo una excepción. Y un proxy que cierra la conexión sin estado no da ningún número: curl imprimió `curl: (56) Proxy CONNECT aborted`, Requests lanzó `ConnectionError` con `RemoteDisconnected('Remote end closed connection without response')`, HTTPX `RemoteProtocolError: Server disconnected without sending a response.` y Chromium `net::ERR_EMPTY_RESPONSE`.

## Comprobación con una sola petición de curl

Imprime los dos códigos de estado de la URL que falla a través del mismo proxy:

```sh
curl --disable -sS -o /dev/null -x "$PROXY_URL" \
  -w 'connect=%{http_connect} http=%{http_code} exit=%{exitcode}\n' \
  https://example.com/
```

- `connect=502 http=000`: el proxy rechazó el túnel; mira las causas de abajo.
- `connect=200 http=502`: el sitio respondió 502; mira [cuando el 502 lo envió el sitio web](#cuando-el-502-lo-envió-el-sitio-web).
- `connect=000` con `Proxy CONNECT aborted`: el proxy cerró la conexión sin estado. Revisa primero el puerto.

## Causas en el lado del proxy

**El sitio no es accesible desde la salida (502).** Falló la conexión del proxy hacia el destino; en el laboratorio, una conexión rechazada se convirtió en `502 Bad Gateway` en todos los clientes. Pide la URL sin proxy y a través de otra salida o país. Si solo falla una salida, el sitio está caído o bloquea esa red desde allí, así que cambia de salida. Si fallan todas, revisa el host y el puerto de tu línea CONNECT.

**La salida está desconectada o rotando (503, a veces 502).** La pasarela no tiene adónde enviar la petición. Reintenta las peticiones idempotentes con espera, como se muestra abajo, y revisa el estado de la salida en el panel de tu proveedor.

**El proxy dejó de esperar (504).** Venció el plazo propio del proxy para llegar al sitio. El proxy del laboratorio esperó 3 segundos y todos los clientes fallaron tras 3,0 a 3,5 segundos. Mantén el timeout de tu cliente por encima del plazo del proxy, o verás tu propio timeout en lugar del 504; la [guía de timeouts de proxy](/es/guides/proxy-timeout-troubleshooting) separa ambos casos.

**Puerto o protocolo equivocado.** Apuntamos todos los clientes, como proxy HTTP, a un puerto que solo habla SOCKS5 (pproxy 2.7.9). El servidor cerró la conexión sin respuesta, así que no hubo ningún 502: curl 8.5.0 y 8.18.0 imprimieron `curl: (56) Proxy CONNECT aborted`, aiohttp lanzó `ServerDisconnectedError` y el fetch de Node no terminó hasta que saltó su timeout de 10 segundos. Copia el esquema, el host y el puerto de la página de configuración de tu proveedor. Una URL de proxy `https://` en un puerto HTTP normal falla de otra forma; consulta [SSL WRONG_VERSION_NUMBER detrás de un proxy](/es/guides/fix-ssl-wrong-version-number-proxy).

**Capacidad del proveedor.** Una pasarela sin capacidad puede responder 502 o 503 a los túneles nuevos. Reduce la concurrencia, espera y envía a tu proveedor las horas de los fallos en UTC.

En el mismo paso, un 407 es cuestión de autenticación ([corregir el error de proxy 407](/es/guides/fix-proxy-error-407)) y un 403 es un rechazo deliberado ([CONNECT tunnel failed, response 403](/es/blog/connect-tunnel-failed-403)).

## Proxies móviles y rotación de IP

En un proxy móvil la salida es un dispositivo en la red de un operador. Mientras cambia de IP puede haber un momento sin salida que funcione; según la pasarela, el cliente ve entonces un 502 o 503 en CONNECT o una conexión cerrada, notificados como se describe arriba. No medimos una rotación real, así que esta guía no da duraciones. Si inicias tú la rotación, pausa las peticiones hasta que funcione una petición de prueba barata.

El laboratorio simuló una salida desconectada durante 4 segundos, con una llamada de Requests por segundo. Los segundos 0 a 3 fallaron con `Tunnel connection failed: 503 Service Unavailable`; desde el segundo 4 todas las peticiones devolvieron 200. Los 4 segundos son un ajuste del laboratorio: el experimento muestra la forma de una rotación, no su duración.

Los reintentos integrados tratan los dos lados de forma distinta. `curl --retry 2` envió el CONNECT al proxy que fallaba una sola vez y se detuvo, pero pidió tres veces al sitio su 502; con `--retry-all-errors` también repitió el CONNECT. `Retry(total=2, status_forcelist=[502, 503, 504])` de urllib3 repitió ambos. El fetch de Node no reintentó nada después de un estado, pero cuando el proxy cerró la conexión sin estado, se reconectó 160.357 veces en 10 segundos, hasta que lo detuvo un `AbortSignal.timeout`. Pasa siempre una señal.

Reintenta solo peticiones GET. Un CONNECT fallido nunca llegó al sitio, pero un 502 del sitio puede llegar después de que el sitio hiciera el trabajo, y un POST reintentado se ejecuta dos veces; consulta [una respuesta perdida, dos trabajos creados](/es/blog/proxy-retries-duplicate-jobs). Esta función reintenta un GET tras un 502, 503 o 504 del proxy o del sitio con esperas limitadas y aleatorias, y se detiene ante cualquier otra cosa, como un 407:

```python
import random, re, time
import requests

RETRYABLE = {502, 503, 504}
TUNNEL_STATUS = re.compile(r"Tunnel connection failed: (\d{3})")

def get_with_retry(session, url, attempts=5, base_delay=1.0, max_delay=8.0, **kwargs):
    for attempt in range(1, attempts + 1):
        try:
            response = session.get(url, **kwargs)
        except requests.exceptions.ProxyError as exc:
            match = TUNNEL_STATUS.search(str(exc))  # Requests reports the CONNECT status only as text
            if match is None or int(match.group(1)) not in RETRYABLE or attempt == attempts:
                raise
        else:
            if response.status_code not in RETRYABLE or attempt == attempts:
                return response
        time.sleep(min(max_delay, base_delay * 2 ** (attempt - 1)) * random.uniform(0.5, 1.0))
```

En la ejecución registrada superó la ventana de 4 segundos sin salida en su cuarto intento, tras 5,7 segundos. Frente a un sitio que seguía respondiendo 502 se rindió tras cinco intentos y 9,9 segundos, y devolvió el 502.

## Cuando el 502 lo envió el sitio web

Si el túnel se abrió, el proxy hizo su trabajo: el 502 vino de la pasarela, el balanceador de carga o la CDN del sitio, que no obtuvieron una respuesta útil de la aplicación que tienen detrás. Cambiar de proxy o rotar la IP no lo arregla. Pide la URL sin proxy; si también falla, espera y reintenta los GET con la función de arriba. Si solo falla a través del proxy, puede que el sitio esté sirviendo errores a esa red; compara el cuerpo y las cabeceras con una petición directa antes de cambiar de salida.

## Reproducir el laboratorio

La [carpeta de descarga](/downloads/fix-proxy-tunnel-502-503/README.md) contiene `lab.mjs` (un proxy en `127.0.0.1` que responde a CONNECT con 502, 503 o 504 o corta la conexión, y un origen TLS que responde 502 por sí mismo), un script por cliente, `wrong_port.mjs`, `node_checks.mjs` y `retry_get.py` con la función de arriba. [results.json](/downloads/fix-proxy-tunnel-502-503/results.json) registra las versiones: curl 8.5.0 y 8.18.0, Python 3.14.4, Requests 2.34.2, urllib3 2.8.0, HTTPX 0.28.1, aiohttp 3.14.5, Node.js 24.22.1 con undici 7.30.1 y Playwright 1.63.0 con Chromium 153. Sin probar: macOS y Windows, proxies SOCKS y HTTPS, y pasarelas reales de proveedores.

Dentro de un agente de programación, [Proxy Toolkit MCP](/mcp) ofrece diagnóstico de errores; dale el cliente y el error depurado, nunca credenciales.

Los [proxies móviles](/pricing) de ipvolt son dispositivos dedicados en redes de operadores; rotas la IP cuando quieras o según un horario, así que una rotación nunca te pilla por sorpresa.

## Fuentes y lecturas adicionales

- [RFC 9110: CONNECT](https://www.rfc-editor.org/rfc/rfc9110.html#name-connect)
- [RFC 9110: 502 Bad Gateway](https://www.rfc-editor.org/rfc/rfc9110.html#name-502-bad-gateway)
- [RFC 9110: 503 Service Unavailable](https://www.rfc-editor.org/rfc/rfc9110.html#name-503-service-unavailable)
- [RFC 9110: 504 Gateway Timeout](https://www.rfc-editor.org/rfc/rfc9110.html#name-504-gateway-timeout)
- [curl manual: --retry](https://curl.se/docs/manpage.html#--retry)
- [curl manual: --retry-all-errors](https://curl.se/docs/manpage.html#--retry-all-errors)
- [urllib3 documentation: Retry](https://urllib3.readthedocs.io/en/stable/reference/urllib3.util.html#urllib3.util.Retry)
- [Requests documentation: proxies](https://requests.readthedocs.io/en/latest/user/advanced/#proxies)
- [HTTPX documentation: proxies](https://www.python-httpx.org/advanced/proxies/)
- [aiohttp documentation: proxy support](https://docs.aiohttp.org/en/stable/client_advanced.html#proxy-support)
- [Playwright: HTTP proxy](https://playwright.dev/docs/network#http-proxy)
- [Node.js CLI documentation: NODE_USE_ENV_PROXY](https://nodejs.org/api/cli.html#node_use_env_proxy1)
- [pproxy on PyPI](https://pypi.org/project/pproxy/)

## Guías relacionadas

- [Corrige el error de proxy 407 sin adivinar](https://ipvolt.com/es/guides/fix-proxy-error-407.md)
- [Corrige ERR_TUNNEL_CONNECTION_FAILED en Playwright y Puppeteer](https://ipvolt.com/es/guides/fix-err-tunnel-connection-failed.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-proxy-tunnel-502-503.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-proxy-tunnel-502-503#waitlist-closing)

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

