Ubuntu 24.04 “Connection reset by peer”: demostrar si fue cliente, proxy o servidor (caso n.º 74)

¿Te fue útil?

“Connection reset by peer” es el equivalente en redes a un encogimiento de hombros. El socket estaba vivo, y luego ya no. Los registros de tu aplicación culpan a la red, tu equipo de red culpa a la aplicación, tu proxy se declara inocente y el canal de incidentes se llena de impresiones en vez de pruebas.

Este artículo trata de convertir ese encogimiento de hombros en una confesión firmada. En Ubuntu 24.04 puedes demostrar si el reset vino del cliente, del proxy o del servidor —hasta el paquete, el PID, el timeout y la capa exacta donde se tomó la decisión.

Qué significa realmente “connection reset by peer” (y qué no)

A nivel de syscall, “connection reset by peer” normalmente se corresponde con ECONNRESET. Tu proceso intentó leer o escribir en un socket TCP y el kernel te dijo: la otra parte dinamita la conexión.

En términos de TCP, la “dinamitación” suele ser un paquete con la bandera RST. Ese RST puede enviarse por varias razones:

  • El peer reinició explícitamente la conexión (la aplicación cerró de forma abrupta, o el kernel decidió que el socket es inválido).
  • Un middlebox forjó un reset (proxy, firewall, load balancer, NAT, IDS). El “peer” puede estar mintiendo con buena postura.
  • El host recibió tráfico para una conexión que no reconoce (no hay socket, se perdió el estado, puerto cerrado) y respondió con RST.

Qué no es:

  • No es un timeout. Los timeouts suelen ser ETIMEDOUT o timeouts a nivel de aplicación (por ejemplo, el cliente HTTP se rinde). Un reset es inmediato y explícito.
  • No es un cierre limpio. Un cierre limpio es FIN/ACK y produce EOF en la lectura, no ECONNRESET.
  • No es necesariamente “el servidor se cayó”. A veces el cliente se fue, a veces el proxy aplicó una política, a veces conntrack hizo una actuación de desaparición.

Dos reglas prácticas que se cumplen en producción:

  1. Si no capturaste paquetes, no sabes quién envió el RST. Los registros pueden insinuarlo. Los paquetes prueban.
  2. Si hay un proxy, asume que está involucrado hasta que se demuestre lo contrario. Los proxies están para interferir.

Guía de diagnóstico rápido (primero/segundo/tercero)

Esta es la versión “estoy de guardia y son las 02:13”. El objetivo es identificar el cuello de botella (cliente, proxy o servidor) lo suficientemente rápido para detener la hemorragia.

Primero: localizar el origen del reset por punto de vista

  1. Elige un flujo fallido: IP del cliente, IP del proxy, IP del servidor, puerto de destino y ventana de tiempo (±30 segundos).
  2. Captura paquetes en el proxy y en el servidor simultáneamente (o lo más cerca posible). Si solo puedes hacerlo en uno, hazlo en el proxy.
  3. Busca el primer RST en cada captura e identifica qué interfaz lo vio primero y desde qué tupla IP/puerto.

Segundo: correlacionar con procesos y registros

  1. En el host emisor, mapea la 4‑tupla a un socket y al proceso propietario con ss -tnpi.
  2. Revisa los logs del proxy para resets upstream vs abortos de cliente vs timeouts (son palabras distintas; no juntes todo a la ligera).
  3. Revisa los logs del servidor por errores en accept, alertas TLS, excepciones de aplicación, reinicios de workers.

Tercero: validar causas sistémicas

  1. Exhaustión de conntrack (ruta NAT o firewall) y agotamiento de puertos efímeros (lado cliente) pueden crear resets “aleatorios”.
  2. Problemas de MTU/PMTUD pueden parecer resets cuando los dispositivos se comportan mal, especialmente con túneles/VPNs.
  3. Retropresión y timeouts: un proxy con timeouts agresivos reseteará upstreams “lentos”. Lento puede ser CPU, almacenamiento, GC o contención de locks.

Broma #1: La forma más rápida de encontrar al culpable es añadir un proxy frente a él—ahora tienes dos culpables y una invitación a reunión.

Un modelo de prueba: cómo dejar de adivinar y empezar a atribuir culpa

Cuando intentas probar de dónde vino un RST, necesitas un método que sobreviva a la política, no solo a la física. Aquí está el modelo que uso en postmortems:

1) Define el flujo y sus “anclas de verdad”

Una sola petición puede atravesar: cliente → NAT → proxy de entrada → proxy de servicio → backend. Las únicas cosas que puedes tratar como “anclas de verdad” son:

  • Capturas de paquetes en múltiples puntos.
  • Estado de sockets del kernel (ss, /proc), idealmente en el host que envió el RST.
  • Logs con marcas de tiempo y IDs de correlación que realmente se propaguen.

2) Usa una línea de tiempo, no impresiones

Buscas una secuencia como esta:

  • SYN/SYN-ACK/ACK establece la conexión.
  • Fluye data (o no).
  • Una parte envía RST (o un dispositivo lo inyecta).

El “quién” es el salto que primero emitió ese RST, no el host que registró la primera excepción.

3) Prueba la direccionalidad con la 4‑tupla y números ACK

Los paquetes RST no son solo banderas. Incluyen números de secuencia/acknowledgment. Cuando capturas en ambos extremos, a menudo puedes decir si un reset es:

  • Generado localmente por el host que estás sniffando (sale por la interfaz con enrutamiento correcto, MAC esperada, patrones de TTL esperados).
  • Reenviado (visto entrante en un proxy desde upstream, y luego separado visto saliente al cliente, a veces con diferente mapeo de puertos).
  • Inyectado (TTL extraño, MAC/vendor OUI inesperado, o aparece solo en un lado).

4) Decide qué significa “peer” en tu arquitectura

Si el cliente habla con un proxy, el “peer” desde la perspectiva del cliente es el proxy. El servidor backend puede ser inocente mientras el proxy resetea. No es semántica; esto cambia qué equipo lo arregla.

5) Trata los timeouts como decisiones de política

Muchos resets no son “fallos”; son enforcement. Ejemplo: el proxy ve upstream inactivo demasiado tiempo y envía RST al cliente para liberar recursos. Es una decisión de producto con disfraz de red.

Datos interesantes y contexto histórico (lo útil)

  • Dato 1: El comportamiento del RST en TCP se formalizó temprano; los resets existen para matar conexiones inválidas rápido en vez de esperar timeouts. Por eso los resets son tan bruscos.
  • Dato 2: Muchos incidentes clásicos de “connection reset” en los 2000 fueron en realidad timeouts de NAT en firewalls stateful. El NAT se olvida de ti; tus paquetes se vuelven “de la nada”.
  • Dato 3: Linux puede enviar RST cuando llega un paquete para un puerto sin socket escuchando—común durante despliegues donde el proceso reinicia y pierde el estado de accept.
  • Dato 4: Los proxies popularizaron la distinción “client abort” vs “upstream reset” como categorías separadas; Nginx/HAProxy/Envoy modernos suelen poder indicar qué dirección falló primero.
  • Dato 5: La “inyección de RST” se ha usado históricamente para censura e interferencia. En redes empresariales, el primo menos dramático es el “appliance de seguridad que termina flujos”.
  • Dato 6: HTTP/2 y HTTP/3 cambiaron algunos modos de fallo: un reset de HTTP/2 aniquila muchas streams a la vez, amplificando el impacto.
  • Dato 7: Ubuntu 24.04 trae un kernel y userland más recientes; cambios en valores por defecto (por ejemplo, control de congestión, bibliotecas TLS, servicios systemd) pueden mover dónde aparece un reset, incluso si tu app no cambió.
  • Dato 8: Keepalives se inventaron para evitar conexiones semi-abiertas eternas, pero keepalive agresivo + timeout NAT corto aún puede crear el tipo de reset “funcionaba ayer”.

Tareas prácticas: comandos, salidas y decisiones (12+)

Estas tareas están escritas para ejecutarse en hosts Ubuntu 24.04. Cada una incluye: el comando, qué significa la salida y qué decisión tomar.

Task 1: Confirmar que el error es realmente ECONNRESET y dónde aparece

cr0x@server:~$ journalctl -u myapp --since "10 min ago" | tail -n 30
Dec 31 01:58:12 api-1 myapp[21450]: error: read ECONNRESET from 10.20.4.18:443
Dec 31 01:58:12 api-1 myapp[21450]: request_id=9b2a... upstream=payments duration_ms=212

Qué significa: La aplicación vio un reset mientras leía. Eso sugiere que el peer (desde la perspectiva de este host) envió RST o un dispositivo intermedio lo hizo.

Decisión: Identifica qué es “peer” en este salto. Si 10.20.4.18:443 es un VIP de proxy, empieza por el proxy.

Task 2: Resolver la ruta de red: ¿quién es realmente el peer?

cr0x@server:~$ ip route get 10.20.4.18
10.20.4.18 via 10.20.1.1 dev ens5 src 10.20.2.17 uid 0
    cache

Qué significa: El tráfico hacia el peer sale por ens5 y gateway 10.20.1.1. No hay overlay inesperado aquí.

Decisión: Captura en ens5. Si la ruta pasa por un dispositivo túnel, sospecha también de MTU y PMTUD.

Task 3: Instantánea de salud TCP en vivo (servidor o proxy)

cr0x@server:~$ ss -s
Total: 1218
TCP:   842 (estab 311, closed 377, orphaned 2, timewait 355)

Transport Total     IP        IPv6
RAW       0         0         0
UDP       14        12        2
TCP       465       419       46
INET      479       431       48
FRAG      0         0         0

Qué significa: Mucho TIME_WAIT es normal en HTTP con mucho tráfico; que “orphaned” esté bajo es bueno. Un conteo enorme de “orphaned” puede indicar cierres abruptos y presión del kernel.

Decisión: Si TIME_WAIT explota en el lado cliente y estás cerca del límite de puertos efímeros, considera reutilización de conexiones y revisar el rango de puertos (tareas posteriores).

Task 4: Encontrar el socket exacto y el proceso propietario para una conexión fallida

cr0x@server:~$ ss -tnpi dst 10.20.4.18:443 | head
ESTAB 0 0 10.20.2.17:51644 10.20.4.18:443 users:(("myapp",pid=21450,fd=78)) timer:(keepalive,116min,0)

Qué significa: La conexión existe y pertenece a myapp. El timer de keepalive está activo; la app está reutilizando conexiones.

Decisión: Si los resets ocurren en conexiones reutilizadas, sospecha de timeouts de inactividad en proxies/NATs. Revisa configuración de timeouts del proxy y timers de NAT.

Task 5: Capturar paquetes en el host donde sospechas que se origina el RST

cr0x@server:~$ sudo tcpdump -i ens5 -nn -s 0 -vvv 'host 10.20.4.18 and tcp port 443' -c 30
tcpdump: listening on ens5, link-type EN10MB (Ethernet), snapshot length 262144 bytes
01:58:12.041223 IP 10.20.2.17.51644 > 10.20.4.18.443: Flags [P.], seq 182:496, ack 911, win 501, length 314
01:58:12.042007 IP 10.20.4.18.443 > 10.20.2.17.51644: Flags [R.], seq 911, ack 496, win 0, length 0
30 packets captured

Qué significa: El peer (10.20.4.18) envió un RST inmediatamente después de recibir datos. Eso no es un timeout; es una negativa activa a mitad de flujo.

Decisión: Céntrate en los logs y procesos del peer. Si 10.20.4.18 es un VIP de proxy, captura en el host del proxy para ver si recibió un RST upstream o lo generó.

Task 6: Capturar en el proxy: probar si es upstream o del lado cliente

cr0x@server:~$ sudo tcpdump -i ens5 -nn -s 0 -vvv '(host 10.20.2.17 and tcp port 443) or (host 10.30.7.55 and tcp port 8443)' -c 50
tcpdump: listening on ens5, link-type EN10MB (Ethernet), snapshot length 262144 bytes
01:58:12.041980 IP 10.20.2.17.51644 > 10.20.4.18.443: Flags [P.], seq 182:496, ack 911, length 314
01:58:12.042001 IP 10.20.4.18.443 > 10.20.2.17.51644: Flags [R.], seq 911, ack 496, length 0
01:58:12.041700 IP 10.20.4.18.41210 > 10.30.7.55.8443: Flags [P.], seq 991:1305, ack 4402, length 314
01:58:12.041750 IP 10.30.7.55.8443 > 10.20.4.18.41210: Flags [P.], seq 4402:4520, ack 1305, length 118

Qué significa: El proxy reinició la conexión al cliente mientras el upstream aún respondía con normalidad. Eso es un reset generado por el proxy.

Decisión: Deja de culpar al backend. Audita timeouts del proxy, límites de tamaño de petición, configuraciones TLS y salud de workers. El proxy está tomando la decisión.

Task 7: Revisar logs de Nginx para abortos de cliente vs reset upstream (si es Nginx)

cr0x@server:~$ sudo tail -n 20 /var/log/nginx/error.log
2025/12/31 01:58:12 [info] 1192#1192: *884 client prematurely closed connection while reading client request body, client: 10.20.2.17, server: api.example, request: "POST /v1/payments HTTP/1.1"
2025/12/31 01:58:12 [error] 1192#1192: *885 upstream prematurely closed connection while reading response header from upstream, client: 10.20.2.17, server: api.example, request: "GET /v1/status HTTP/1.1", upstream: "http://10.30.7.55:8443/v1/status"

Qué significa: Dos problemas distintos. “client prematurely closed” apunta a un aborto por parte del cliente. “upstream prematurely closed” apunta al backend cerrando pronto (puede ser crash, desajuste de keepalive, cierre de la app o timeout upstream).

Decisión: Separa incidentes por la redacción de los logs. Trátalos como árboles de causa raíz distintos, no como un gran “fallo de red”.

Task 8: Revisar estado de terminación en HAProxy (si es HAProxy)

cr0x@server:~$ sudo tail -n 5 /var/log/haproxy.log
Dec 31 01:58:12 lb-1 haproxy[2034]: 10.20.2.17:51644 [31/Dec/2025:01:58:12.041] fe_https be_api/api-3 0/0/1/2/3 200 512 - - ---- 12/12/0/0/0 0/0 "GET /v1/status HTTP/1.1"
Dec 31 01:58:12 lb-1 haproxy[2034]: 10.20.2.17:51645 [31/Dec/2025:01:58:12.050] fe_https be_api/api-2 0/0/0/0/1 0 0 - - cD-- 3/3/0/0/0 0/0 "POST /v1/payments HTTP/1.1"

Qué significa: Flags de terminación como cD-- sugieren fuertemente aborto por parte del cliente (“client” + “data”). La petición nunca se completó.

Decisión: Si HAProxy reporta abortos de cliente pero los clientes niegan haberlos hecho, verifica timeouts del cliente e intermediarios (redes móviles, proxies corporativos, valores por defecto de SDK).

Task 9: Revisar Envoy por razones de reset upstream (si es Envoy)

cr0x@server:~$ sudo journalctl -u envoy --since "10 min ago" | tail -n 10
Dec 31 01:58:12 edge-1 envoy[1640]: [debug] upstream reset: reset reason: connection termination, transport failure reason: delayed connect error: 111
Dec 31 01:58:12 edge-1 envoy[1640]: [debug] downstream connection termination

Qué significa: Envoy te da una razón de reset upstream y a menudo una razón de fallo de transporte. “connect error: 111” es connection refused (RST del host upstream porque no hay listener).

Decisión: Trata “connection refused” como un problema de disponibilidad del backend (servicio no escuchando, puerto equivocado, despliegue fallido), no como resets aleatorios.

Task 10: Revisar contadores del kernel por dolor de listen/backlog

cr0x@server:~$ netstat -s | egrep -i 'listen|overflow|reset' | head -n 20
    14 times the listen queue of a socket overflowed
    14 SYNs to LISTEN sockets ignored
    2318 connection resets received
    1875 connections reset sent

Qué significa: Overflow en la cola de listen puede causar que los clientes vean resets o fallos de conexión bajo ráfagas. “reset sent” indica que este host está activamente reseteando peers.

Decisión: Si el overflow de listen aumenta durante incidentes, ajusta backlog (somaxconn, backlog de accept en la app) o escala. No toques timeouts primero; solo enmascararás el síntoma.

Task 11: Revisar exhaustión de conntrack (común en cajas NAT/proxy)

cr0x@server:~$ sudo sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max
net.netfilter.nf_conntrack_count = 262131
net.netfilter.nf_conntrack_max = 262144

Qué significa: Básicamente estás lleno. Una vez saturado conntrack, los nuevos flujos se dropean o se manglean. Los errores resultantes pueden incluir resets, timeouts y handshakes parciales extraños.

Decisión: Aumenta nf_conntrack_max si la memoria lo permite, reduce timeouts para flujos muertos y disminuye churn de conexiones (keepalive, pooling). También encuentra la fuente de la explosión de conexiones.

Task 12: Validar rango de puertos efímeros del cliente y presión de TIME_WAIT

cr0x@server:~$ sysctl net.ipv4.ip_local_port_range
net.ipv4.ip_local_port_range = 32768	60999

Qué significa: Aproximadamente 28k puertos disponibles por IP origen. Con muchas conexiones de corta vida puedes agotarlos y tener fallos de conexión que se manifiestan como resets upstream.

Decisión: Favorece keepalive/pooling; si debes, amplía el rango de puertos y considera más IPs origen (pools SNAT) en clientes de alto volumen.

Task 13: Revisar agujeros negros de MTU y fallos de PMTUD

cr0x@server:~$ ping -M do -s 1472 10.20.4.18 -c 3
PING 10.20.4.18 (10.20.4.18) 1472(1500) bytes of data.
From 10.20.2.17 icmp_seq=1 Frag needed and DF set (mtu = 1450)
From 10.20.2.17 icmp_seq=2 Frag needed and DF set (mtu = 1450)
From 10.20.2.17 icmp_seq=3 Frag needed and DF set (mtu = 1450)

--- 10.20.4.18 ping statistics ---
3 packets transmitted, 0 received, +3 errors, 100% packet loss, time 2025ms

Qué significa: La MTU de ruta es 1450, no 1500. Si algo bloquea ICMP “frag needed”, algunas sesiones TCP se atascan y luego son terminadas por intermedios, a veces vía resets.

Decisión: Arregla la consistencia de MTU (túneles, VXLAN), permite ICMP para PMTUD o ajusta MSS en los bordes.

Task 14: Confirmar si el handshake TLS está siendo reseteado

cr0x@server:~$ openssl s_client -connect 10.20.4.18:443 -servername api.example -tls1_2 
CONNECTED(00000003)
write:errno=104

Qué significa: Errno 104 es ECONNRESET. Reset durante handshake apunta a políticas TLS del proxy (SNI requerido, ciphers, ALPN), rate limiting o un backend que no está hablando TLS en ese puerto.

Decisión: Compara con un cliente conocido bueno, verifica SNI, inspecciona config TLS del proxy y captura paquetes para ver quién envía el RST.

Task 15: Inspeccionar drops y errores en la interfaz

cr0x@server:~$ ip -s link show dev ens5
2: ens5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
    link/ether 06:3a:9e:11:22:33 brd ff:ff:ff:ff:ff:ff
    RX:  bytes packets errors dropped  missed   mcast
    1287349832 1298374      0     412       0       0
    TX:  bytes packets errors dropped carrier collsns
    983748233  992331      0       9       0       0

Qué significa: RX drops es distinto de cero. Las pérdidas pueden provocar retransmisiones y activar políticas de timeout que acaban en resets.

Decisión: Si los drops se correlacionan con los tiempos del incidente, investiga cola de NIC, saturación de CPU del host y congestión upstream. No persigas fantasmas de aplicación hasta que el trazado de paquetes sea estable.

Task 16: Vincular un reset a una regla de firewall local (nftables/ufw)

cr0x@server:~$ sudo nft list ruleset | sed -n '1,120p'
table inet filter {
  chain input {
    type filter hook input priority 0; policy accept;
    tcp dport 443 ct state new limit rate 50/second accept
    tcp dport 443 ct state new reject with tcp reset
  }
}

Qué significa: Estás enviando explícitamente TCP resets cuando se excede la tasa. Eso no es “la red”. Es política.

Decisión: Si los resets aumentan en ráfagas, ajusta rate limits, añade buffering/queueing o escala. Y documenta la regla para que el próximo de guardia no la redescubra a las 3 a.m.

Modos de fallo específicos de proxies (Nginx, HAProxy, Envoy)

Si usas cualquier tipo de proxy, estás en el negocio de “connection reset by peer”, te guste o no. Aquí están los patrones que importan.

El proxy resetea al cliente porque el upstream es lento

Común cuando:

  • Los headers de respuesta upstream toman demasiado (proxy_read_timeout en Nginx; timeouts en HAProxy/Envoy).
  • Los buffers del proxy están llenos; decide matar conexiones en vez de hundirse con el barco.

Cómo probar: Captura en el proxy muestra RST hacia el cliente sin un RST correspondiente upstream en ese momento. Los logs del proxy muestran timeout/504/terminación.

Solución: Decide si aumentar timeouts o arreglar la latencia del upstream. Aumentar timeouts es apostar a que la lentitud es “normal”. En producción, la lentitud rara vez es normal; es un síntoma.

El proxy resetea por límites de tamaño de body o headers

Los proxies pueden cerrar conexiones agresivamente por headers/bodys demasiado grandes. Dependiendo de la configuración y el timing, el cliente puede ver un reset en lugar de un 413/431 ordenado.

Cómo probar: Logs de error del proxy mencionan “client sent too large request” o fallos de parsing de headers. La captura muestra RST poco después de que el cliente envía headers/body.

Solución: Aumenta límites con cuidado. Si los aumentas globalmente, también aumentas el blast radius para abuso y presión de memoria.

Desajuste de keepalive entre proxy y upstream

Una de las trampas más comunes en producción: el proxy reutiliza conexiones upstream más tiempo del que el upstream tolera. El upstream cierra conexiones inactivas. El proxy intenta escribir en un socket muerto. Ocurre el reset en algún punto y tu mensaje de error apunta al salto equivocado.

Cómo probar: En el proxy: conexión upstream en ss muestra timers de keepalive; logs upstream muestran cierres por timeout de inactividad; captura muestra FIN/RST upstream en conexión inactiva seguido por intento de escritura del proxy.

Solución: Alinea timeouts de keepalive: SDK cliente, proxy de borde, proxy de servicio, servidor backend y NAT. Elige una jerarquía (borde el más corto o backend el más corto) y documenta.

Política TLS (SNI/ALPN/ciphers) que parece resets aleatorios

Los fallos TLS suelen registrarse mal por las aplicaciones y a veces los proxies los manejan con cierres duros. Un cliente sin SNI puede recibir un reset. Un cliente antiguo con ciphers débiles puede recibir reset. La negociación HTTP/2 complica esto cuando ALPN está involucrado.

Cómo probar: openssl s_client muestra reset durante handshake; logs del proxy muestran errores de handshake; captura muestra reset justo después del ClientHello.

Solución: Aplica política TLS, pero hazla observable. Si vas a resetear, registra por qué con suficiente detalle para actuar.

Causas del lado servidor: app, kernel, TLS, almacenamiento (sí, almacenamiento)

Los resets a veces son culpa del servidor de forma aburrida: el servidor está sobrecargado, mal configurado o reiniciándose, y el kernel hace lo que hacen los kernels—empeorar el día de tu app.

Reinicios de la app y churn de conexiones

Si tu backend se reinicia bajo carga (OOM, crash loop, deploy), las conexiones existentes pueden caerse. Los clientes ven resets si el proceso muere o si un sidecar/proxy desmonta sockets bruscamente.

Prueba: Correlaciona timestamps de reset con reinicios del servicio en journalctl y eventos del orquestador. La captura muestra resets coincidiendo con SYNs hacia un host sin listener.

Overflow de backlog de listen

Un servidor puede estar “up” y aun así no aceptar conexiones. Si la cola de accept se desborda, los SYNs se dropean o se ignoran; los clientes reintentan; los intermediarios reaccionan; eventualmente alguien resetea.

Prueba: netstat -s muestra overflows en listen; CPU del servidor puede estar al máximo; logs de la app muestran accepts lentos.

Solución: Ajusta backlog, escala y reduce overhead por conexión. No lo escondas con timeouts más grandes.

Latencia de almacenamiento que se disfraza de resets de red

Aquí es donde los ingenieros de almacenamiento entran en “problemas de red”. Si tus hilos de servidor se bloquean en disco (fsyncs de base de datos, saturación de volumen de logs, fallos EBS, escrituras ZFS sync), la latencia de peticiones explota. Los proxies hitan timeouts y resetean clientes. Los clientes culpan al servidor. El servidor culpa a la red. Nadie culpa al disco porque el disco está “verde”.

Prueba: Picos de latencia en métricas de aplicación coinciden con timeouts de proxy y resets de cliente. En el servidor, iostat y pidstat muestran IO wait durante la ventana del incidente.

Solución: Trata la latencia de almacenamiento como parte de la ruta de la petición. Pónle SLOs. Si no lo mides, no lo puedes exonerar.

Confusión por offload de TLS

Backends hablando HTTP en un puerto donde el proxy espera TLS (o viceversa) pueden producir resets que parecen flaqueza de handshake. Es un clásico de deriva de configuración.

Prueba: Captura muestra texto plano donde debería haber TLS, o reset inmediato tras ClientHello. Logs del proxy muestran “wrong version number” o fallo de handshake.

Solución: Haz explícitos los contratos puerto/protocolo. Considera “siempre ha sido así” como un reporte de bug, no como razón.

Causas del lado cliente: abortos, NATs, MTU y bibliotecas “útiles”

Los clientes causan una cantidad sorprendente de resets. No por malicia—por impaciencia, timeouts por defecto y middleboxes que recogen estado agresivamente.

Abortos de cliente por timeout local

Los SDKs a menudo tienen timeouts cortos por defecto. Los clientes móviles son peores porque las redes son peores. Los clientes de escritorio corporativos pueden ser los peores porque los proxies de seguridad son creativos.

Prueba: Logs del proxy dicen “client prematurely closed connection” o el estado de terminación de HAProxy indica aborto del cliente. Captura en el proxy muestra FIN/RST desde el lado cliente primero.

Solución: Deja explícitos los timeouts del cliente y alinéelos con comportamiento del servidor/proxy. Si necesitas 30 segundos para una petición, no lances un timeout cliente de 5 segundos y luego culpes al servidor.

Timeouts de inactividad de NAT matando conexiones de larga duración

Un cliente detrás de NAT puede mantener una conexión TCP inactiva más tiempo que el timeout del NAT. El NAT olvida el mapeo. El siguiente paquete sale, el tráfico de retorno no encuentra estado y algún dispositivo responde con RST o drop.

Prueba: Los resets ocurren tras periodos inactivos; cambiar intervalos de keepalive cambia la tasa de fallos. Las tablas conntrack del NAT muestran timeouts cortos para flujos establecidos.

Solución: Keepalives en intervalos menores que el timeout del NAT, o evita conexiones de larga inactividad en redes hostiles.

Desajuste de MTU y agujeros negros

Los problemas de MTU no deberían causar resets. En un mundo perfecto causan fragmentación o ajuste PMTUD. En el mundo real, algunos dispositivos bloquean ICMP y otros toman decisiones “útiles” como matar flujos.

Prueba: ping -M do muestra MTU de ruta menor a la esperada; la captura muestra retransmisiones y luego resets por parte del proxy tras timeouts.

Solución: Arregla la MTU de ruta o ajusta MSS en el borde de túneles.

Broma #2: “Probablemente es el cliente” no es un diagnóstico; es un mecanismo de afrontamiento con número de ticket.

Tres microhistorias del mundo corporativo

Microhistoria 1: El incidente causado por una suposición equivocada

Tenían una narrativa limpia: “La base de datos está reiniciando conexiones”. El error era ECONNRESET, el stack trace apuntaba al driver de BD y el servicio hacía timeouts bajo carga. Así que el equipo de BD fue pagado, otra vez, y todos prepararon el baile habitual.

Un ingeniero senior hizo la pregunta grosera pero necesaria: “¿De dónde vino el reset?” Nadie lo sabía. Tenían métricas, dashboards y una sala de guerra; no tenían captura de paquetes.

Capturaron en dos puntos: el host de aplicación y la capa HAProxy. En el host de aplicación, el RST parecía venir del VIP de HAProxy. En HAProxy, no había RST upstream desde la BD. En cambio, HAProxy estaba emitiendo resets porque su timeout del lado cliente era más corto que el percentil más lento de consultas BD durante picos de IO wait.

La suposición equivocada fue que el “peer” en el mensaje de error era la BD. En realidad el peer era el proxy. La BD estaba lenta, sí, pero no estaba reiniciando nada; el proxy estaba aplicando política.

La solución no fue “aumentar todos los timeouts para siempre”. Aumentaron ligeramente el timeout del proxy, pero el trabajo real fue reducir stalls de IO—programar mantenimiento de índices, mejores planes de consulta y mover una carga de logs fuera del mismo volumen. Después de eso, el proxy dejó de “ayudar”.

Microhistoria 2: La optimización que se volvió en contra

Un equipo intentó reducir latencia y CPU aumentando la reutilización de keepalive por todas partes. Menos handshakes, menos setups TCP, menos overhead TLS. En papel parecía ordenado.

Lo desplegaron primero en el proxy de borde, dejando que las conexiones upstream vivieran mucho más. En un día, clientes reportaron resets esporádicos en peticiones ordinarias. Los logs eran enfurecedores: algunas peticiones funcionaban instantáneamente, otras morían a mitad de petición con “connection reset by peer”.

La causa raíz fue un desajuste: el servidor de aplicación upstream tenía un timeout de inactividad agresivo y a veces reciclaba workers. El proxy, ahora manteniendo conexiones upstream más tiempo, reutilizaba sockets que habían sido silenciosamente cerrados. La primera escritura en una conexión medio-muerta disparó un reset. Bajo alta concurrencia parecía aleatorio.

Las capturas dejaban claro: upstream envió FIN por inactividad; el proxy no lo notó a tiempo; el proxy escribió; el kernel respondió con comportamiento RST. La “optimización” aumentó la probabilidad de golpear sockets upstream obsoletos.

La solución fue aburrida: alinear timeouts, habilitar health checks activos que validen realmente peticiones y fijar keepalive upstream a un valor menos heroico. El rendimiento mejoró después de que dejaron de intentar ser demasiado listos.

Microhistoria 3: La práctica correcta y aburrida que salvó el día

Un equipo de plataforma tenía una regla: todo incidente cara al cliente requiere una “captura bidireccional” si involucra errores de red. La gente se quejaba porque parecía lento y procedimental. Lo era, y ese era el punto.

Una tarde, un servicio interno empezó a fallar con resets. El equipo de aplicación insistía en que era el proxy. El equipo de proxy insistía en que era la app. El equipo de red insistía en que era “upstream”. El canal de incidentes empezó a oler a comité.

Siguieron la regla. Capturaron en la interfaz cara a cliente del proxy y en su interfaz hacia upstream. El reset era visible viniendo de un hop de firewall entre proxy y backend, sólo en el lado upstream. El proxy solo estaba transmitiendo el dolor.

Porque tuvieron evidencia temprana, evitaron una hora de cambios de configuración inútiles. La ventana de cambio del firewall mostró un ajuste reciente que rechazaba ciertos flujos con TCP reset tras un umbral de tasa. No era malicioso; era “protector”.

La solución fue ajustar la regla de firewall para acomodar el perfil legítimo de ráfagas del servicio y añadir una excepción explícita para la subred del proxy. La tasa de resets bajó inmediatamente. Nadie tuvo que fingir que “sentía” el problema.

Errores comunes: síntoma → causa raíz → solución

  • Síntoma: Resets solo en POSTs grandes.
    Causa raíz: Límite de tamaño de body del proxy o comportamiento de buffering; a veces WAF termina flujos.
    Solución: Eleva límites específicos (client_max_body_size), ajusta buffering, confirma política WAF; verifica con captura y logs del proxy.
  • Síntoma: Resets ocurren tras ~60 segundos de inactividad en conexiones reutilizadas.
    Causa raíz: Timeout de NAT/proxy más corto que el periodo de reuse de keepalive del cliente.
    Solución: Alinea keepalive/timeouts de inactividad; considera tuning de TCP keepalive; reduce reuse inactivo.
  • Síntoma: Tráfico en ráfagas causa resets/connection refused inmediatos.
    Causa raíz: Overflow de listen backlog o rate limiting que rechaza con TCP reset.
    Solución: Ajusta backlog, escala o cambia rate limiting; confirma con netstat -s y reglas de firewall.
  • Síntoma: Solo ciertos clientes (SDKs antiguos) ven resets durante handshake TLS.
    Causa raíz: Aplicación de política TLS: SNI faltante, ciphers no soportados, ALPN desajustado; proxy cierra en frío.
    Solución: Haz explícitos los requisitos TLS; mejora reporting de errores; prueba con openssl s_client usando ajustes similares al cliente.
  • Síntoma: Resets se correlacionan con despliegues pero solo un porcentaje del tráfico falla.
    Causa raíz: Draining no configurado; el proxy envía tráfico a instancias reiniciando; sockets upstream keepalive obsoletos.
    Solución: Añade shutdown gracioso, readiness gates, draining y keepalive upstream más corto.
  • Síntoma: Resets “aleatorios” bajo alto churn de conexiones; hosts NAT/proxy muestran comportamiento raro.
    Causa raíz: Tabla conntrack casi llena; drops/evictions crean pérdida de estado.
    Solución: Aumenta conntrack max, reduce churn, ajusta timeouts, identifica los top talkers.
  • Síntoma: Resets se agrupan con drops y retransmisiones de paquetes.
    Causa raíz: Drops en interfaz, overruns de cola, saturación de CPU o congestión de ruta que llevan a timeouts de política y resets.
    Solución: Arregla pérdida de paquetes primero; luego ajusta timeouts. Usa ip -s link y capturas para confirmar.
  • Síntoma: Resets aparecen solo a través de VPN/túneles.
    Causa raíz: Desajuste de MTU o ICMP bloqueado causando fallo de PMTUD; proxies terminan flujos lentos/atascados.
    Solución: Clamp MSS, alinea MTU, permite ICMP “frag needed”, valida con pings DF.

Listas de verificación / plan paso a paso

Plan paso a paso: probar cliente vs proxy vs servidor en menos de una hora

  1. Elige una petición fallida: timestamp, IP cliente, VIP de destino, ruta de la petición, correlation ID si está disponible.
  2. Identifica el primer salto proxy (ingress/load balancer). En stacks modernos, ese suele ser el “peer” real para el cliente.
  3. Captura paquetes en dos puntos:
    • En el proxy: interfaz cara a cliente.
    • En el proxy: interfaz hacia upstream (o en el host backend).
  4. Encuentra el primer RST y registra:
    • IP:puerto origen y IP:puerto destino
    • Hora exacta
    • Si es [R.] o [R] y si fue en respuesta a data
  5. Correlaciona con logs en el host que envió el RST:
    • Frases de error de Nginx
    • Flags de terminación de HAProxy
    • Razones de reset upstream de Envoy
    • Contadores del kernel (netstat -s)
  6. Revisa restricciones sistémicas (rápido):
    • conteo conntrack vs max
    • drops en interfaz
    • saturación de CPU / IO wait
  7. Toma una decisión:
    • Si el proxy generó RST: arregla config/timeouts/política del proxy.
    • Si el backend generó RST: arregla ciclo de vida de la app, listener, TLS o sobrecarga.
    • Si el cliente generó RST: arregla timeout/retry del cliente o la ruta de red del cliente.
    • Si un middlebox lo inyectó: arregla política del firewall/WAF/NAT o tablas de estado.

Checklist operativo: qué registrar para un informe de incidente defensible

  • snippets pcap mostrando el RST en dos puntos de vista
  • la(s) 4‑tupla(s) implicada(s) y cualquier mapeo NAT si se conoce
  • líneas de log del proxy para la misma petición
  • evidencia de reinicio o sobrecarga del servidor (journald, métricas)
  • estado de conntrack y rango de puertos si es relevante
  • los knobs de configuración exactos implicados (timeouts, límites, rate limits)

Preguntas frecuentes

1) ¿“Connection reset by peer” siempre significa que el servidor remoto se cayó?

No. Significa que alguien en el otro extremo de ese salto TCP envió un reset—o un middlebox se hizo pasar por ellos. En arquitecturas proxied, el “peer” a menudo es el proxy.

2) ¿Cómo pruebo que el proxy envió el reset?

Captura en el proxy. Si el proxy envía RST al cliente mientras el lado upstream no muestra un RST correspondiente (e incluso está enviando datos), el proxy es el origen.

3) ¿Puede un firewall causar resets en lugar de drops?

Sí. Firewalls y WAFs a menudo “reject with tcp reset” para fallar rápido. Esto es común con rate limits, estado inválido o violaciones de política.

4) ¿Por qué sólo lo veo en conexiones keepalive?

Porque las conexiones obsoletas se reutilizan. Los timeouts de NAT, timeouts de proxy y timeouts backend no se alinean automáticamente. La primera escritura después de inactividad es donde descubres que hablabas con un fantasma.

5) ¿Es realmente necesaria la captura de paquetes? ¿Puedo arreglármelas con logs?

Los logs pueden sugerir; los paquetes prueban. Cuando hay varios equipos involucrados, “sugerir” te gana reuniones. La prueba te gana una solución.

6) ¿Cuál es la diferencia práctica entre FIN y RST?

FIN es un cierre educado: las lecturas devuelven EOF. RST es un aborto duro: read/write falla con ECONNRESET. Muchos proxies y motores de política eligen RST para recuperar recursos rápidamente.

7) ¿Pueden los problemas de almacenamiento realmente llevar a resets de conexión?

Indirectamente, sí. La latencia de almacenamiento puede hacer que los backends sean lentos, lo que dispara timeouts de proxy; el proxy entonces resetea al cliente. El reset es un síntoma; la causa raíz puede ser IO wait.

8) ¿Cómo distingo abortos de cliente de abortos de servidor?

Los logs del proxy son normalmente la pista más rápida (cliente cerró prematuramente vs upstream cerró). Para prueba, una captura de paquetes muestra qué lado envió FIN/RST primero.

9) ¿Qué hago si veo resets pero nadie está registrando nada?

Entonces te falta observabilidad en la capa que tomó la decisión. Añade logging estructurado en proxies (razón de terminación), guarda pcaps cortos durante incidentes y registra estado de sockets.

Conclusión: siguientes pasos prácticos

No arreglas “connection reset by peer”. Arreglas el componente que decidió enviar un reset—o la condición que lo forzó.

Siguientes pasos que se pagan solos:

  1. Adopta la regla de captura bidireccional para incidentes de red. Acaba con las discusiones rápido.
  2. Normaliza presupuestos de timeout entre clientes, proxies y servidores. Escríbelos. Hazlos parte de la revisión de cambios.
  3. Haz los resets observables: razones de terminación del proxy, contadores de rechazo del firewall, alertas de saturación de conntrack, alertas de overflow de backlog.
  4. Ejecuta las tareas anteriores durante el incidente y pega las salidas en el ticket. El tú del futuro le enviará una nota de agradecimiento al tú del pasado.

Idea parafraseada (atribución): Werner Vogels ha enfatizado que debes “construir sistemas que asuman fallos y se recuperen rápido”, porque la falla es normal en sistemas distribuidos.

← Anterior
local-lvm de Proxmox al 100%: encuentra qué llenó tu thin pool y arréglalo
Siguiente →
Fail2ban para correo: reglas que realmente detectan ataques

Deja un comentario