Problema

En entornos de producción basados en Windows Server, es frecuente observar que la aplicación web desaparece de la red durante uno o dos minutos y luego vuelve a estar disponible sin intervención. Los monitores externos (Pingdom, Uptime Kuma) reportan socket timeout o unable to connect to server. Lo curioso es que los logs de IIS, del framework ASP.NET Core y de la base de datos aparecen vacíos o sin errores claros. El síntoma se repite de forma aleatoria: a veces bajo carga, a veces en periodos de baja actividad, y ocurre en diferentes regiones geográficas.

Este patrón – interrupciones breves, sin trazas evidentes y con errores de conexión a nivel de socket – es típico de fallos en la capa de transporte o en los componentes que gestionan la cola de peticiones (HTTP.sys, ThreadPool, conexiones a bases de datos o a clústers de búsqueda). La falta de información en los logs obliga a buscar señales en métricas de bajo nivel y a correlacionar varios subsistemas antes de llegar a la raíz.

Causa

Las causas más habituales que generan socket timeout intermitente en un stack Windows + IIS + ASP.NET Core son:

  1. Agotamiento del ThreadPool o de la cola de solicitudes de HTTP.sys
    Cuando el número de peticiones concurrentes supera la capacidad configurada, HTTP.sys empieza a rechazar conexiones y el cliente recibe un timeout. El problema puede aparecer aunque la CPU no esté al 100 % porque el bloqueo está en la gestión de sockets.

  2. Límites de conexiones simultáneas a recursos externos

    • SQL Server: pool de conexiones agotado o tiempo de espera de la base de datos demasiado bajo.
    • Elasticsearch: número máximo de conexiones HTTP o saturación del nodo.
      Cuando una capa externa se vuelve lenta, los hilos de ASP.NET quedan bloqueados y el ThreadPool se llena.
  3. Problemas de red internos

    • Saturación de la interfaz de red del servidor (picos de tráfico, NIC en modo half‑duplex).
    • Configuración de TCP/IP que provoca TIME_WAIT excesivo o limitaciones de MaxUserPort.
  4. Configuración de IIS/HTTP.sys

    • Cola de solicitudes (QueueLength) demasiado pequeña.
    • Timeout de conexión (connectionTimeout) o de petición (headerWaitTimeout) configurados con valores agresivos.
  5. Bloqueos de código
    Código síncrono o llamadas bloqueantes dentro de ASP.NET Core pueden mantener hilos ocupados durante varios segundos, reduciendo la disponibilidad del ThreadPool y provocando rechazos de nuevas conexiones.

  6. Reciclado inesperado del Application Pool
    Un reciclado forzado (por memoria, tiempo o cambios en archivos) reinicia el proceso w3wp.exe, dejando un breve lapso sin respuestas.

Solución

Una estrategia reutilizable se basa en tres pilares: instrumentación temprana, correlación de métricas y pruebas de carga controlada.

1. Instrumentación de métricas de bajo nivel

Métrica Por qué es útil Herramienta recomendada
ThreadPool (threads disponibles / en uso) Detecta agotamiento antes de que los timeouts aparezcan. Performance Counter \ASP.NET Applications\Thread Count o OpenTelemetry.
Cola de HTTP.sys (CurrentQueueLength, RejectedRequests) Muestra si IIS está rechazando peticiones por saturación. netsh http show servicestate o logman para exportar counters.
Contadores de TCP (TcpConnections, TcpTimedWaitDelay) Identifica puertos en estado TIME_WAIT que pueden agotar el rango de puertos. Get-NetTCPConnection en PowerShell.
Latencia y tasa de error de SQL/Elasticsearch Correlaciona picos de latencia externa con los timeouts. Exporters de Prometheus para SQL Server y Elastic.
Uso de CPU / Memoria del proceso w3wp.exe Descarta cuellos de botella de hardware. Process Explorer, PerfMon.

2. Activar logs de HTTP.sys y de IIS

  1. HTTPERR – registra errores de nivel socket antes de que lleguen a IIS.

    reg add "HKLM\SYSTEM\CurrentControlSet\Services\HTTP\Parameters" /v EnableErrorLogging /t REG_DWORD /d 1 /f
    netsh http add iplisten ipaddress=0.0.0.0
    

    Reinicia el servicio HTTP para que el archivo httperr.log empiece a recibir entradas.

  2. IIS Advanced Logging – habilita campos como cs-uri-stem, sc-status, time-taken y win32-status. Configura un filtro para capturar solo códigos 0 (éxitos) y 10060 (timeout).

3. Correlación en tiempo real

Crea un dashboard en Grafana que combine:

  • ThreadPool y QueueLength (líneas de tiempo).
  • Latency de SQL y Elastic (histogramas).
  • TCP connections (gauge).
  • Eventos de HTTPERR (tabla).

Marca los eventos de timeout de los monitores externos y observa qué métricas presentan picos simultáneos.

4. Pruebas de carga controlada

Ejecuta una prueba de carga ligera (por ejemplo, k6 run script.js) que reproduzca el patrón de tráfico típico. Aumenta gradualmente la concurrencia hasta que aparezca el timeout. Durante la prueba, revisa los contadores para identificar el punto de quiebre. Esta fase permite validar hipótesis sin esperar a un incidente real.

5. Ajustes rápidos

  • Incrementar QueueLength en el sitio IIS:
    appcmd set config "Default Web Site" -section:system.applicationHost/sites /[name='Default Web Site'].limits.queueLength:5000 /commit:apphost
    
  • Ajustar ThreadPool mediante ThreadPool.SetMinThreads al iniciar la aplicación (código C#).
  • Aumentar MaxUserPort y reducir TcpTimedWaitDelay para evitar agotamiento de puertos:
    netsh int ipv4 set dynamicport tcp start=10000 num=55535
    reg add "HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" /v TcpTimedWaitDelay /t REG_DWORD /d 30 /f
    
  • Revisar y optimizar llamadas bloqueantes: sustituir Result o Wait() por await asíncrono siempre que sea posible.

6. Implementar OpenTelemetry

Despliega un agente que capture trazas distribuidas (request → SQL → Elasticsearch). Las trazas revelarán dónde se acumulan latencias y permitirán filtrar los requests que terminan en timeout.

Cuándo aplicar esta solución

  • Síntomas: timeout de socket reportado por monitores externos, ausencia de errores en logs de aplicación/IIS, interrupciones breves y aleatorias.
  • Entorno: Windows Server con IIS, aplicaciones ASP.NET Core, dependencias externas (bases de datos, clústers de búsqueda).
  • No aplica: fallos persistentes con códigos HTTP 5xx claros, problemas de DNS, o cuando el servidor está completamente inactivo (p.ej., apagado o reinicio). En esos casos la investigación se centra en la capa de infraestructura o en la disponibilidad del host.

Código

# Habilitar logging de HTTPERR
reg add "HKLM\SYSTEM\CurrentControlSet\Services\HTTP\Parameters" /v EnableErrorLogging /t REG_DWORD /d 1 /f
net stop http
net start http

# Aumentar rango de puertos efímeros y reducir TIME_WAIT
netsh int ipv4 set dynamicport tcp start=10000 num=55535
reg add "HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" /v TcpTimedWaitDelay /t REG_DWORD /d 30 /f

# Incrementar cola de IIS
appcmd set config "Default Web Site" -section:system.applicationHost/sites /[name='Default Web Site'].limits.queueLength:5000 /commit:apphost

Verificación

  1. Reproducir el timeout con la prueba de carga.
  2. Confirmar que los contadores de ThreadPool y QueueLength muestran valores cercanos al máximo justo antes del fallo.
  3. Verificar que el archivo httperr.log contiene entradas con Win32Status=10060 (timeout) en los momentos esperados.
  4. Corregir los valores (p.ej., aumentar QueueLength) y repetir la prueba. Si el timeout desaparece, la causa está confirmada.
  5. Mantener el dashboard activo durante al menos 24 h para validar que los picos ya no coinciden con incidentes reales.

Notas adicionales

  • En servidores con alta rotación de sockets, el rango de puertos efímeros puede agotarse rápidamente; ajustar MaxUserPort a 65534 y TcpTimedWaitDelay a 30 s suele ser suficiente.
  • OpenTelemetry tiene bajo impacto de rendimiento, pero habilitarlo solo en producción puede generar gran volumen de datos; usa filtros por nivel de error o por endpoint crítico.
  • Si después de los ajustes persisten los timeouts, considera introducir un reverse proxy ligero (NGINX o ARR) que ofrezca buffering y métricas de conexión adicionales sin cambiar la arquitectura principal.
  • Documenta cualquier cambio de configuración en un control de versiones (por ejemplo, scripts PowerShell en Git) para que el proceso de rollback sea inmediato en caso de efectos colaterales.