El punto de partida
Un timeout te dice que un reloj expiró. Encuentra la última etapa completada antes de cambiar el plazo, el gateway o la política de reintentos.
Traza la ruta que realmente agotó el tiempo
Para un destino HTTPS a través de un proxy HTTP, la ruta habitual es: resolver el proxy, conectarse a él por TCP, obtener un túnel CONNECT con éxito, completar el TLS del destino, enviar la petición y después recibir las cabeceras y el cuerpo. Un proxy HTTPS añade su propio handshake TLS antes de CONNECT. Las dos conexiones TLS tienen configuraciones de confianza separadas.
En esta configuración, normalmente es el proxy quien resuelve el nombre de host del destino. Por tanto, una medición rápida del DNS local no dice nada sobre la resolución del destino en el proxy. SOCKS5 puede resolver localmente o de forma remota, según la configuración del cliente; identifica esa elección antes de interpretar un error de DNS.
Empieza con un endpoint GET pequeño y aprobado y un solo gateway. Mantén la autenticación y el enrutamiento explícitos. Una navegación en el navegador o un trabajo de aplicación también pueden esperar por un pool de conexiones, redirecciones, scripts o procesamiento local; su timeout no es automáticamente un timeout del proxy.
Captura un único intento sin traza
Este ejemplo de shell requiere curl 8.3 o posterior, porque curl importa por sí mismo las variables de autenticación. Comprueba curl --version, incluidos su backend TLS y sus características. Define PROXY_URL con un gateway HTTP(S) sin credenciales proporcionado por tu proveedor; https://proxy.example.invalid:8443 es una ilustración que deliberadamente no funciona. Inyecta PROXY_USERNAME y PROXY_PASSWORD de forma privada. Este ejemplo usa autenticación de proxy Basic; adapta la autenticación solo al método documentado por el proveedor.
Guárdalo como proxy-timing.sh y ejecuta sh proxy-timing.sh. CHECK_URL toma por defecto https://example.com/; puedes sustituirlo por un endpoint HTTPS pequeño que estés autorizado a probar. Usa URL sin credenciales incrustadas ni parámetros sensibles. El script descarta el cuerpo, imprime algunos resultados numéricos seleccionados y conserva el estado de salida de curl. No sigue redirecciones ni reintenta. No lo ejecutes con el rastreo del shell activado. --disable sigue en primer lugar para ignorar la configuración de curlrc, y --noproxy con un valor vacío evita que una regla NO_PROXY heredada se salte el gateway seleccionado.
proxy-timing.sh · curl 8.3+ · un GET HTTPS
: "${PROXY_URL:?Set a credential-free HTTP(S) proxy URL}"
CHECK_URL=${CHECK_URL:-https://example.com/}
case "$PROXY_URL" in
http://*|https://*) ;;
*) printf '%s\n' 'PROXY_URL must use http:// or https://' >&2; exit 2 ;;
esac
case "$CHECK_URL" in
https://*) ;;
*) printf '%s\n' 'CHECK_URL must use https://' >&2; exit 2 ;;
esac
case "$PROXY_URL $CHECK_URL" in
*'@'*|*'?'*|*'#'*)
printf '%s\n' 'Use URLs without credentials, queries or fragments' >&2
exit 2 ;;
esac
curl --disable --silent --fail --http1.1 \
--noproxy '' --proxy "$PROXY_URL" --proxy-basic \
--variable %PROXY_USERNAME --variable %PROXY_PASSWORD \
--expand-proxy-user '{{PROXY_USERNAME}}:{{PROXY_PASSWORD}}' \
--connect-timeout 5 --max-time 15 --retry 0 \
--output /dev/null \
--write-out 'exit=%{exitcode} http=%{http_code} tunnel=%{http_connect} dns=%{time_namelookup} tcp=%{time_connect} tls=%{time_appconnect} ready=%{time_pretransfer} first=%{time_starttransfer} total=%{time_total} bytes=%{size_download}\n' \
"$CHECK_URL"Lee hitos, no temporizadores independientes
Los campos de tiempo son segundos medidos desde el inicio de la transferencia. No son duraciones separadas que haya que sumar. Este ejemplo arranca un proceso curl nuevo y pide HTTP/1.1 para que una sola petición sea más fácil de inspeccionar. Las conexiones reutilizadas, la multiplexación, las cadenas de proxies y las redirecciones seguidas requieren una interpretación adicional. Un campo a cero puede reflejar redondeo, una etapa no alcanzada o una etapa que no aplicaba; no demuestra una operación instantánea.
Trata las diferencias como pistas solo cuando ambos hitos se completaron en esta petición simple. Por ejemplo, tls menos tcp incluye el intercambio CONNECT y el handshake del destino en un proxy HTTP; no es una medición del TLS del destino por sí solo. first menos ready incluye el envío de la petición y la espera a través de la red y del servidor. No aísla el tiempo de CPU del destino.
- dns = time_namelookup: finalización de la resolución en el lado del cliente. Con esta configuración de proxy, el nombre de host relevante es el gateway, no la resolución del destino en el lado del proxy.
- tcp = time_connect: la conexión con el proxy se completó. Esto no establece que el túnel o la conexión con el destino funcionen.
- tls = time_appconnect: la configuración TLS se completó. ready = time_pretransfer: la configuración del protocolo llegó al punto en que puede comenzar la transferencia. Un proxy HTTPS introduce otro handshake; estos campos no ofrecen tiempos separados para cada salto.
- first = time_starttransfer: primer byte de respuesta, incluida la configuración previa. Interprétalo junto con tunnel y http; una respuesta recibida del proxy no es evidencia de éxito en el destino.
- total = time_total y bytes = size_download: tiempo de transferencia transcurrido y bytes del cuerpo descargados. Un HTTP 200 con exit 28 puede seguir siendo una descarga incompleta.
Sigue la última etapa completada
Usa el siguiente orden para el ejemplo de petición única. Detente en la primera etapa sin resolver. Las comprobaciones sugeridas acotan la investigación; los tiempos por sí solos no pueden identificar qué máquina descartó un paquete ni explicar la cola interna de un proveedor.
- Sin dirección del proxy: exit 5 significa que curl no pudo resolver el proxy. Verifica la ortografía del gateway y el resolvedor disponible dentro del contenedor o servicio real. Exit 6 se refiere a un nombre de host que curl intentó resolver localmente; revisa el enrutamiento y el modo DNS antes de culpar al DNS del destino en el lado del proxy.
- Sin conexión TCP: exit 7 indica fallo de conexión; exit 28 antes de que tcp se complete puede indicar que el presupuesto de conexión expiró. Comprueba el puerto configurado, la ruta de salida y el firewall desde el mismo runtime. Un rechazo inmediato y una caída silenciosa de la red requieren comprobaciones distintas.
- TCP completado, tunnel sigue en 000: no hay ninguna respuesta CONNECT utilizable registrada. Para un proxy HTTPS, su propia configuración TLS puede ser la etapa sin terminar. Para un proxy HTTP, investiga la negociación CONNECT, la disponibilidad del gateway y el puerto de destino permitido. Se necesitan diagnósticos del proveedor para separar sus fallos de DNS del destino, de conexión y de política.
- tunnel es 407: sigue la guía de autenticación de proxy. Un timeout mayor no corregirá las credenciales. Otras respuestas CONNECT distintas de 2xx requieren revisar la política del proxy y el significado del error documentado por el proveedor; no son estados HTTP del destino.
- tunnel es 200, TLS del destino sin terminar: el proxy aceptó el túnel, pero la configuración HTTPS no se completó. Exit 35 indica un fallo del handshake TLS y exit 60 un fallo de verificación del certificado. Comprueba el nombre del destino, la confianza en la CA aprobada y el reloj; mantén activada la verificación de certificados. Un timeout aquí también puede deberse a una ruta del túnel estancada.
- TLS y ready completados, http es 000: investiga la espera de una respuesta del destino, incluidas la aplicación remota y la ruta de vuelta a través del proxy. Comprueba un endpoint de control aprobado a través del mismo gateway y después de nuevo el endpoint original; cambia una variable cada vez.
- http es 200, bytes se detuvo y exit es 28: llegaron las cabeceras, pero el cuerpo no terminó dentro del límite. Compara el tamaño de respuesta esperado con el progreso de la transferencia. Aumentar el timeout de conexión no resuelve esta etapa.
- http es 504: un gateway HTTP informó de su propio timeout hacia el upstream. Con --fail, el ejemplo normalmente sale con 22. Esto difiere del exit 28 de curl, en el que expiró un límite de tiempo del lado del cliente; investiga qué gateway generó la respuesta antes de cambiar el plazo del cliente.
Da a cada intento una parte del plazo del trabajo
En curl, --connect-timeout cubre el establecimiento de la conexión, incluidos el DNS y las negociaciones de protocolo necesarias; no es solo un temporizador de TCP. --max-time cubre toda la transferencia, incluida la configuración de la conexión y la transferencia del cuerpo. El presupuesto de conexión está dentro del presupuesto de transferencia. Los cinco y quince segundos del ejemplo son valores iniciales para un diagnóstico pequeño, no garantías del servicio ni valores predeterminados universales para producción.
Una aplicación sigue necesitando un plazo global que cubra las esperas del pool, los intentos, el backoff, el manejo del cuerpo y la limpieza. Trabaja hacia atrás desde el límite del llamador. Para un trabajo ilustrativo de veinte segundos, podrías reservar dos segundos para trabajo local, permitir un intento inicial de ocho segundos, esperar un segundo y permitir como máximo un reintento de ocho segundos si queda tiempo suficiente. Usa un reloj monotónico y limita cada intento siguiente al presupuesto restante; no reinicies el plazo del trabajo al reintentar.
Un timeout de lectura del socket generalmente limita un intervalo de espera de datos, no el trabajo completo. Un progreso lento puede esquivarlo repetidamente. El --speed-limit de curl junto con --speed-time puede abortar transferencias persistentemente lentas, pero eso es una política de rendimiento mínimo, no un sustituto preciso del timeout de lectura inactiva de una aplicación. El long polling y el streaming necesitan una política ajustada a sus pausas esperadas, además de cancelación y una vida útil global cuando corresponda.
Reintenta solo cuando repetir sea seguro y útil
La configuración base deliberadamente no tiene reintentos. Tras localizar el fallo, un reintento acotado puede ser razonable para un GET o HEAD aprobado cuya semántica en la aplicación sea segura e idempotente, y para un fallo que esperas que sea temporal. La semántica de los métodos HTTP es un punto de partida; un endpoint que desencadena una acción a pesar de usar GET necesita su propia revisión. Un timeout después de una escritura deja el resultado incierto. Comprueba el estado de la operación o su mecanismo de idempotencia documentado antes de volver a enviarla.
Para un GET seguro ya revisado, añadir --retry 1 --retry-max-time 20 permite como máximo un reintento para las condiciones transitorias que curl admite. No convierte los veinte segundos en un plazo estricto del trabajo: --max-time se reinicia en cada intento, y un intento iniciado dentro de la ventana de reintento puede terminar después de esa ventana. Mantén un plazo externo cuando el llamador necesite un límite firme. Respeta Retry-After y detente cuando la espera requerida no quepa; los bucles de reintento de la aplicación deben usar backoff acotado con jitter y un límite de concurrencia.
- No reintentes repetidamente errores de autenticación, certificado, URL o política que no hayan cambiado. Corrige primero la configuración.
- No añadas --retry-all-errors a un cliente genérico como arreglo para timeouts. Eso amplía la repetición a fallos cuya recuperación y efectos secundarios no se han revisado.
- No reproduzcas un pago, el envío de un formulario, el envío de un mensaje u otra petición que cambie estado solo porque se perdió su respuesta. Una respuesta perdida no demuestra que la operación fallara.
- No uses reintentos para forzar el paso a través de un bloqueo ni para aumentar la carga sobre un destino sobrecargado. Reduce la concurrencia y respeta los límites del servicio.
Lo que establecieron las comprobaciones locales
Revisado el 11 de septiembre de 2026 con curl 8.7.1 en macOS, con su build SecureTransport/LibreSSL declarada. Unos fixtures controlados en loopback ejercitaron un intercambio CONNECT de proxy HTTP y un destino HTTPS local con un certificado de prueba explícitamente confiable. Un CONNECT estancado y un handshake TLS del destino estancado salieron ambos con 28 cerca de un presupuesto de conexión de 0,3 segundos, pero solo el segundo tenía tunnel=200. Un handshake TLS estancado de un proxy HTTPS, probado por separado, también salió antes de CONNECT.
Una vez completado TLS, unas cabeceras estancadas y un cuerpo estancado alcanzaron en cambio un presupuesto total de 1,2 segundos. El caso del cuerpo conservó http=200 y un byte descargado. Un HTTP 504 suministrado devolvió exit 22. Un CONNECT retrasado aumentó tls menos tcp, lo que confirma que esta diferencia incluye la configuración del túnel. Un fixture de reintento con una ventana de reintento de dos segundos e intentos de 0,7 segundos se ejecutó durante unos 2,4 segundos, lo que confirma que la ventana de reintento por sí sola no es un plazo total estricto. Su campo total final describía el último intento, de modo que un trabajo con reintentos también necesita una medición externa del tiempo transcurrido.
Estos fixtures establecen el comportamiento del cliente bajo fallos controlados. No evalúan el rendimiento de un proveedor, no ejercitan caídas reales de DNS, no validan todas las builds de curl ni demuestran cómo se comportan HTTP/2, SOCKS o un proxy multisalto. Conserva junto al incidente la versión del runtime, la etapa, los estados, los tiempos, el recuento de bytes y el número de intentos. Comparte solo detalles sin datos sensibles; las URL de destino, las cabeceras y las trazas pueden contener datos privados. Los ejemplos no establecen la disponibilidad del servicio de ipvolt ni el soporte de su gateway.
Antes de publicar
- Registra el runtime real, el esquema del proxy, el esquema del destino y el modo DNS.
- Captura un intento explícito antes de añadir redirecciones, concurrencia o reintentos.
- Usa juntos los códigos de estado y los hitos de tiempo completados para elegir la siguiente comprobación.
- Mantén los presupuestos de conexión y de transferencia dentro de un plazo global del trabajo.
- Reintenta solo trabajo seguro e idempotente aprobado dentro del presupuesto restante.
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.
- Everything curl: proxy connections and DNS responsibility
- curl manual: variables, numeric output and retry-window limits
- libcurl: connection-completion timing
- libcurl: TLS-completion timing
- libcurl: pre-transfer timing
- libcurl: time to first response byte
- Everything curl: exit-code meanings
- libcurl: connection timeout within the total timeout
- libcurl: average-speed timeout policy
- Requests: read timeouts are not whole-download limits
- Everything curl: retry behavior
- RFC 9110: idempotent retry semantics and gateway statuses