“Connection reset by peer” es el equivalente en redes a un encogimiento de hombros. Algo, en algún lugar, cerró la puerta en medio de la conversación. Tu aplicación informa un error. El negocio reporta una caída. Y tres equipos empiezan inmediatamente un animado juego de “no fui yo”.
Este expediente trata de acabar con ese juego. En Ubuntu 24.04, normalmente puedes probar quién envió el reset (cliente, proxy/balanceador de carga o servidor) con un puñado de capturas, contadores y registros—si recopilas la evidencia correcta en el orden correcto.
Qué significa realmente “connection reset by peer” (y qué no)
A nivel de cable, un “reset” es TCP diciendo: “Alto. No vamos a hacer esto.” Eso es un segmento RST. No es un timeout, no es pérdida de paquetes (aunque la pérdida puede llevar a ello) y no implica necesariamente “el servidor se cayó”. Es una terminación explícita que aborta una conexión inmediatamente.
Las aplicaciones lo ven como:
- ECONNRESET (común en Node.js, Go, Python, Java)
- recv() failed (104: Connection reset by peer) (común en registros de Nginx)
- Connection reset by peer de curl/OpenSSL cuando el peer aborta durante HTTP/TLS
Matiz importante: “peer” significa “la otra parte desde el punto de vista de la aplicación.” Si tu cliente habla con un proxy reverso, el “peer” es el proxy. Si tu proxy habla con un upstream, el “peer” es el upstream. Si un firewall inyecta un reset, el “peer” es efectivamente “quien falsificó el reset,” por eso capturamos paquetes para identificar el emisor.
Los resets TCP aparecen por varias razones legítimas:
- El proceso cerró un socket de forma abrupta (o el SO lo hizo por el proceso).
- La otra parte recibió datos para una conexión que no reconoce (sin estado), por lo que envía RST.
- Un middlebox (proxy, firewall, NAT) decide que tu flujo es indeseable y envía RST.
- Un timeout o agotamiento de recursos hace que un componente pierda estado; paquetes posteriores desencadenan RST.
Y aquí la conclusión práctica: el camino más rápido hacia la verdad es encontrar el dispositivo que envió el RST. Esto no es filosofía. Es tcpdump.
Guion de diagnóstico rápido (primero/segundo/tercero)
Cuando estás de guardia, no quieres un seminario de redes. Quieres una secuencia que reduzca el radio de impacto en minutos.
Primero: clasifica el reset (orientado al cliente o al upstream)
- Si los usuarios acceden a través de un proxy/LB, revisa si los logs del proxy muestran reset del cliente o reset del upstream.
- Si el servidor de la aplicación está expuesto directamente, revisa los registros y estadísticas de sockets en el servidor para ver resets.
Segundo: captura el RST en el salto controlado más cercano
- Captura en la interfaz del proxy/LB si existe; se sitúa entre mundos.
- Si no hay proxy, captura en la NIC del servidor.
- Si controlas el cliente (monitor sintético o bastión), captura también allí.
Tercero: correlaciona con tiempo, tupla y dirección
- Empareja por la 5-tupla (IP origen, puerto origen, IP destino, puerto destino, protocolo).
- Identifica quién envía el RST observando la IP/MAC de origen del paquete y el punto de captura.
- Confirma la razón del componente usando logs/contadores (logs de app, logs de proxy, contadores del kernel, conntrack, errores TLS).
Si solo recuerdas una cosa: no discutas sobre culpables hasta que puedas señalar un frame RST y decir “esta caja lo envió.”
Hechos y contexto que cambian cómo depuras
Esto no es trivia. Cada uno cambia qué evidencia confías y qué hipótesis priorizas.
- El RST de TCP precede a la mayoría de tus herramientas. Es comportamiento del núcleo de TCP desde los primeros RFC; tu elegante service mesh es solo el actor más nuevo que envía paquetes clásicos.
- Linux enviará RST si no hay socket escuchando. Si nada está enlazado al puerto de destino, el kernel responde con RST a un SYN, lo que los clientes suelen interpretar como “reset”.
- RST no es “siempre el servidor”. Firewalls, balanceadores, gateways NAT e IDS/IPS pueden generar resets. Los middleboxes lo hacen para fallar cerrado—o para fallar de maneras creativas.
- El desajuste de keep-alive es un generador clásico de resets. Un lado reusa una conexión inactiva tras el tiempo de espera del otro lado; la primera petición nueva recibe un reset.
- La expiración del estado NAT causa “resets fantasma”. Si un dispositivo NAT olvida una asociación mientras los endpoints siguen hablando, el siguiente paquete puede disparar un reset o ser descartado—de cualquier forma parece inestabilidad aleatoria.
- Problemas de Path MTU pueden enmascararse como resets. Técnicamente eso es más a menudo “congelamientos” que resets, pero algunas pilas y middleboxes reaccionan mal y abortan conexiones durante TLS o HTTP/2.
- Ubuntu 24.04 incluye nftables como herramienta predeterminada. iptables puede seguir existiendo como capa de compatibilidad, pero necesitas saber si las reglas están en nft, no en tu memoria.
- QUIC/HTTP/3 movió muchos modos de fallo a UDP. Tu navegador puede “arreglar” un camino TCP roto cambiando de protocolo, haciendo que el reset parezca intermitente y dependiente del agente de usuario.
- La observabilidad cambió la dinámica social. Hace diez años los equipos discutían por “se siente como”. Hoy puedes—y debes—discutir con capturas de paquetes y logs estructurados.
Una idea parafraseada, porque sigue siendo la más útil operativamente: paraphrased idea
— “La esperanza no es una estrategia.” (atribuido en la cultura ops a Edsger W. Dijkstra)
Probar quién lo hizo: cliente vs proxy vs servidor
Pensa en capas de custodia. El reset se origina en algún lugar, atraviesa algunos saltos y luego se convierte en “reset del peer” para quien lo recibe. Tu trabajo es identificar al originador, no a la víctima.
Caso A: el cliente envió el reset (sucede más de lo que la gente admite)
Los resets originados por el cliente suelen aparecer como:
- El navegador navegó fuera o cerró una pestaña a mitad de la petición.
- La app móvil quedó en segundo plano; el SO mató sockets para ahorrar energía.
- Timeout del cliente más corto que el del servidor/proxy; el cliente aborta temprano.
- Una librería de reintentos cancela agresivamente la petición en vuelo.
Cómo probarlo:
- En la captura del servidor o proxy, ves un RST proveniente de la IP del cliente.
- Los logs del servidor muestran “cliente cerró la conexión de forma prematura.”
- El proxy muestra 499 (Nginx) o un estado de terminación consistente con abortos por parte del cliente.
Caso B: el proxy/balanceador envió el reset
Los proxies reinician conexiones por razones que suenan razonables en un documento de diseño y duelen en producción:
- Se alcanzó el timeout de inactividad; el proxy recupera recursos.
- Se alcanzó el máximo de peticiones por conexión; el proxy cierra sin drenado elegante.
- Lógica de health-check o circuit-breaker decide que el upstream está mal y falla rápido.
- Límites de buffer, límites de cabeceras o políticas de cuerpo de petición disparan un aborto.
- Cajas de inspección TLS consideran que tu certificado es “raro”.
Cómo probarlo:
- En captura desde el lado del cliente, la IP fuente del RST es la VIP del proxy/LB o la IP del nodo.
- En la captura del proxy, no ves RST upstream—solo el proxy generando el RST al cliente.
- Los logs del proxy muestran un error local (timeout, límite excedido, upstream no disponible), no un aborto upstream.
Caso C: el servidor envió el reset
Los servidores reinician conexiones por tres grandes categorías de razones:
- Comportamiento de la aplicación: caídas del proceso, pánicos, salidas, cierres abruptos de sockets o rechazo de conexiones por lógica de sobrecarga.
- Comportamiento del kernel: sin listener, desbordamiento de backlog de SYN, presión de memoria o reglas de firewall que rechazan con tcp-reset.
- Comportamiento de infraestructura: agotamiento de conntrack, agotamiento de puertos efímeros en el servidor actuando como cliente o cambios de routing.
Cómo probarlo:
- La captura cerca del servidor muestra RST con origen en la IP del servidor.
- Los logs del servidor coinciden con reinicios, kills por OOM o mensajes de aborto a nivel de aplicación.
- Los contadores del kernel muestran aumentos en escuchas descartadas o abortos durante la ventana del incidente.
Broma #1: Un reset TCP es como que te cuelguen el teléfono a mitad de frase—de repente ya no estás debatiendo arquitectura, estás debatiendo quién colgó.
Tareas de campo: comandos, salidas y decisiones (12+)
Estas son las tareas que realmente ejecuto en sistemas Ubuntu 24.04. Cada una incluye: comando, un fragmento realista de salida, qué significa y qué decisión tomar a continuación. Ejecútalas desde el punto más cercano al síntoma: cliente, proxy o servidor.
Task 1: Reproducir con curl y forzar una conexión única
cr0x@server:~$ curl -vk --http1.1 --no-keepalive https://api.internal.example/v1/ping
* Trying 10.20.30.40:443...
* Connected to api.internal.example (10.20.30.40) port 443 (#0)
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* Recv failure: Connection reset by peer
* OpenSSL SSL_read: Connection reset by peer, errno 104
curl: (56) Recv failure: Connection reset by peer
Significado: El reset ocurrió durante el handshake TLS. Eso acota los sospechosos: proxy que termina TLS, IDS, stack TLS del servidor mal configurado o problemas de camino que hacen que middleboxes aborten.
Decisión: Captura paquetes en el terminador TLS (proxy/LB) primero. Si no hay proxy, captura en la NIC del servidor.
Task 2: Identificar si estás hablando con una VIP de proxy/LB
cr0x@server:~$ getent ahostsv4 api.internal.example
10.20.30.40 STREAM api.internal.example
10.20.30.40 DGRAM api.internal.example
10.20.30.40 RAW api.internal.example
Significado: IP única. Aún podría ser una VIP.
Decisión: Revisa ARP/neigh para ver si parece una VIP (patrones de fabricante MAC ayudan) y pregunta “¿dónde termina TLS?”
Task 3: Confirmar ruta y siguiente salto (lado servidor)
cr0x@server:~$ ip route get 10.20.30.40
10.20.30.40 via 10.10.0.1 dev ens5 src 10.10.2.15 uid 1000
cache
Significado: El tráfico sale vía el gateway 10.10.0.1.
Decisión: Si sospechas de un firewall/NAT, ese gateway es candidato. Captura en ambos lados si puedes.
Task 4: Línea base de estado de sockets y contadores de reset con ss
cr0x@server:~$ ss -s
Total: 1234 (kernel 0)
TCP: 642 (estab 120, closed 410, orphaned 0, timewait 350)
Transport Total IP IPv6
RAW 0 0 0
UDP 18 12 6
TCP 232 180 52
INET 250 192 58
FRAG 0 0 0
Significado: Muchos TIME-WAIT y sockets cerrados no es automáticamente malo, pero huele mal si se dispara durante resets.
Decisión: Si TIME-WAIT explota bajo carga, revisa desconexiones agresivas del cliente, keep-alives cortos o reutilización pobre de conexiones.
Task 5: Inspeccionar estadísticas TCP del kernel para abortos y drops en listen
cr0x@server:~$ nstat -az | egrep 'TcpExtListen|TcpAbort|TcpTimeout|TcpRetrans'
TcpExtListenDrops 18 0.0
TcpExtListenOverflows 5 0.0
TcpAbortOnData 22 0.0
TcpAbortOnTimeout 9 0.0
TcpTimeouts 133 0.0
TcpRetransSegs 420 0.0
Significado: Drops/overflows en listen sugieren que el servidor no pudo aceptar conexiones lo suficientemente rápido (backlog SYN / presión en la cola de accept). Contadores de abortos implican que la pila local abortó conexiones.
Decisión: Si estos suben con los incidentes, deja de culpar al cliente. Arregla backlog de accept, bucle de accept de la app, saturación de CPU o ráfagas de conexiones desde el proxy.
Task 6: Validar que el servidor está realmente escuchando (y en qué)
cr0x@server:~$ sudo ss -ltnp '( sport = :443 )'
State Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
LISTEN 0 4096 0.0.0.0:443 0.0.0.0:* users:(("nginx",pid=1421,fd=6))
Significado: Algo escucha en 443, profundidad de cola 4096 configurada.
Decisión: Si no hay nada escuchando, el kernel RSTea los SYNs. Esa es tu respuesta. Si está escuchando, sigue investigando.
Task 7: Captura de paquetes para identificar quién envía el RST (vantage server)
cr0x@server:~$ sudo tcpdump -ni ens5 'tcp port 443 and (tcp[tcpflags] & (tcp-rst) != 0)' -vv
tcpdump: listening on ens5, link-type EN10MB (Ethernet), snapshot length 262144 bytes
14:22:10.118322 IP 10.20.30.40.443 > 10.10.2.15.51544: Flags [R.], seq 20394822, ack 129388, win 0, length 0
Significado: En el servidor vemos un paquete con origen 10.20.30.40:443 enviando RST al cliente (10.10.2.15). Si 10.20.30.40 es una VIP de proxy, eso implica al proxy. Si es la IP del servidor, implica al servidor.
Decisión: Captura en el nodo proxy. Si el proxy no lo generó, lo verás entrante desde upstream. Si lo generó, no lo verás.
Task 8: Captura en el proxy para separar “reset upstream” vs “reset del proxy”
cr0x@server:~$ sudo tcpdump -ni ens5 'host 10.10.2.15 and tcp port 443' -vv
tcpdump: listening on ens5, link-type EN10MB (Ethernet), snapshot length 262144 bytes
14:22:10.118319 IP 10.10.2.15.51544 > 10.20.30.40.443: Flags [S], seq 129387, win 64240, options [mss 1460,sackOK,TS val 332191 ecr 0,nop,wscale 7], length 0
14:22:10.118322 IP 10.20.30.40.443 > 10.10.2.15.51544: Flags [R.], seq 0, ack 129388, win 0, length 0
Significado: RST inmediato al SYN (seq 0, ack SYN+1) desde 10.20.30.40 sugiere “nada está escuchando” en ese VIP/puerto desde la perspectiva del proxy o una regla de firewall local que rechaza.
Decisión: Revisa listener en el host/contenedor del proxy y reglas de firewall. Si el proxy debe terminar TLS, verifica que el servicio esté arriba y enlazado.
Task 9: Revisar logs de error/acceso de Nginx para abortos del cliente vs abortos del upstream
cr0x@server:~$ sudo tail -n 5 /var/log/nginx/error.log
2025/12/30 14:22:10 [error] 1421#1421: *441 recv() failed (104: Connection reset by peer) while reading response header from upstream, client: 10.10.2.15, server: api.internal.example, request: "GET /v1/ping HTTP/1.1", upstream: "https://10.30.40.50:8443/v1/ping", host: "api.internal.example"
Significado: Nginx (proxy) está diciendo que el upstream reinició la conexión mientras Nginx leía la respuesta upstream.
Decisión: Deja de acusar al cliente. Mueve la investigación al servidor upstream 10.30.40.50:8443 y captura allí.
Task 10: Revisar estado de terminación en HAProxy (si lo usas)
cr0x@server:~$ sudo tail -n 3 /var/log/haproxy.log
Dec 30 14:22:10 lb1 haproxy[2011]: 10.10.2.15:51544 [30/Dec/2025:14:22:10.118] fe_https be_api/srv2 0/0/1/2/3 200 512 - - ---- 12/12/0/0/0 0/0 "GET /v1/ping HTTP/1.1"
Dec 30 14:22:10 lb1 haproxy[2011]: 10.10.2.15:51545 [30/Dec/2025:14:22:10.221] fe_https be_api/srv2 0/0/0/0/0 0 0 - - SD-- 3/3/0/0/0 0/0 "GET /v1/ping HTTP/1.1"
Significado: Las banderas de terminación (aquí “SD–”) sugieren que el lado servidor se cerró. Los códigos exactos varían por configuración, pero el log te dice qué lado terminó la sesión.
Decisión: Si la terminación del lado servidor domina durante la ventana del incidente, céntrate en la estabilidad upstream, timeouts y presión de recursos.
Task 11: Buscar agotamiento de conntrack (síntoma tipo middlebox en NATs Linux)
cr0x@server:~$ sudo sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max
net.netfilter.nf_conntrack_count = 262041
net.netfilter.nf_conntrack_max = 262144
Significado: Básicamente te quedaste sin entradas de conntrack. En ese punto, nuevas conexiones se descartan o manejan mal. Algunos entornos muestran resets; otros muestran timeouts.
Decisión: Aumenta nf_conntrack_max (teniendo en cuenta memoria), reduce timeouts de conntrack donde sea seguro y reduce churn de conexiones (keep-alive, pooling).
Task 12: Revisar reglas nftables por “reject with tcp reset”
cr0x@server:~$ sudo nft list ruleset | sed -n '1,120p'
table inet filter {
chain input {
type filter hook input priority filter; policy drop;
ct state established,related accept
iif "lo" accept
tcp dport 22 accept
tcp dport 443 reject with tcp reset
}
}
Significado: El firewall del host está explícitamente enviando resets TCP en el puerto 443. Eso se manifestará como “connection reset by peer” para los clientes.
Decisión: Corrige la regla. Reemplaza reject por accept para fuentes permitidas, o usa un drop controlado si prefieres timeouts (pero prepárate para fallos lentos).
Task 13: Identificar desajustes de MTU y offload (sutilmente desagradable)
cr0x@server:~$ ip 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 02:ab:cd:ef:12:34 brd ff:ff:ff:ff:ff:ff
Significado: MTU es 1500 en esta interfaz. Si otros segmentos usan tramas jumbo o túneles reducen MTU, problemas de fragmentación/PMTUD pueden aparecer durante TLS.
Decisión: Si sospechas MTU, ejecuta un ping dirigido con DF entre los mismos endpoints y ajusta MTU/MSS en el dispositivo correcto.
Task 14: Probar que PMTUD funciona (o no) con pings DF
cr0x@server:~$ ping -M do -s 1472 10.20.30.40 -c 3
PING 10.20.30.40 (10.20.30.40) 1472(1500) bytes of data.
ping: local error: message too long, mtu=1450
ping: local error: message too long, mtu=1450
ping: local error: message too long, mtu=1450
--- 10.20.30.40 ping statistics ---
3 packets transmitted, 0 received, +3 errors, 100% packet loss, time 2040ms
Significado: Tu MTU efectiva de camino es 1450 (común con VXLAN/GRE/encapsulado cloud). Si tu stack/proxy no ajusta MSS correctamente, puedes ver fallos extraños en handshakes y a veces resets.
Decisión: Clampa MSS en el borde (o configura MTUs apropiadas en interfaces), y vuelve a probar TLS.
Task 15: Revisar journal de systemd alrededor del tiempo exacto de fallo
cr0x@server:~$ sudo journalctl -u nginx --since "2025-12-30 14:20:00" --until "2025-12-30 14:25:00" | tail -n 8
Dec 30 14:22:09 api1 nginx[1421]: 2025/12/30 14:22:09 [warn] 1421#1421: *439 upstream timed out (110: Connection timed out) while reading response header from upstream
Dec 30 14:22:10 api1 nginx[1421]: 2025/12/30 14:22:10 [error] 1421#1421: *441 recv() failed (104: Connection reset by peer) while reading response header from upstream
Dec 30 14:22:11 api1 systemd[1]: nginx.service: Main process exited, code=killed, status=9/KILL
Dec 30 14:22:11 api1 systemd[1]: nginx.service: Failed with result 'signal'.
Dec 30 14:22:12 api1 systemd[1]: nginx.service: Scheduled restart job, restart counter is at 1.
Significado: Ahora tenemos una narrativa: timeouts upstream, luego resets, luego Nginx es matado y reinicia. Esto no es un “parpadeo de red aleatorio.” Algo está matando Nginx (¿OOM killer? ¿despliegue? ¿watchdog?).
Decisión: Revisa dmesg por OOM y revisa la automatización de despliegues. Si Nginx se reinicia, puede resetear conexiones en vuelo.
Task 16: Buscar kills por OOM y quejas del kernel
cr0x@server:~$ dmesg -T | tail -n 8
[Mon Dec 30 14:22:11 2025] Out of memory: Killed process 1421 (nginx) total-vm:512000kB, anon-rss:210000kB, file-rss:12000kB, shmem-rss:0kB, UID:0 pgtables:900kB oom_score_adj:0
[Mon Dec 30 14:22:11 2025] oom_reaper: reaped process 1421 (nginx), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
Significado: El kernel mató a Nginx. Eso producirá absolutamente resets y comportamiento medio abierto raro.
Decisión: Arregla la presión de memoria (límites, fugas, buffering de logs, caches demasiado grandes). Hasta entonces, perseguir “la red” es un hobby, no respuesta a incidentes.
Broma #2: Si adivinas la causa sin una captura de paquetes, básicamente estás haciendo respuesta a incidentes por astrología.
Tres micro-historias corporativas (anonimizadas, plausibles, técnicamente precisas)
1) El incidente causado por una suposición errónea: “El servidor nos reinició”
El síntoma era claro: clientes móviles en una región específica recibían “connection reset by peer” durante el login. El equipo de API no vio nada obvio. Los paneles del balanceador de carga parecían tranquilos. Alguien dijo la frase peligrosa: “Debe ser la app; es lo único que cambió la semana pasada.”
Así que hicieron rollback de la app. Sin cambio. Hicieron rollback de una migración de base de datos. Sin cambio. Empezaron a planear un failover regional. Mientras tanto, soporte aprendió nuevas y creativas formas de decir “por favor inténtelo de nuevo”.
Finalmente, un SRE hizo lo poco glamoroso: tcpdump en los nodos del balanceador, filtrando RST. Los paquetes RST no venían de los servidores API en absoluto. Venían de un appliance de seguridad aguas arriba del balanceador, con la IP del appliance, y solo para rangos ASN de clientes específicos.
La causa raíz fue una actualización de threat-intel que etiquetó erróneamente un bloque NAT compartido de un operador móvil como hostil. El appliance empezó a inyectar resets para handshakes TLS que coincidían con una heurística. El “peer” en los logs del cliente era la VIP del balanceador, pero el emisor no era ni LB ni servidor—era la caja que nadie quería admitir que existía.
La solución fue simple y política: whitelist, luego cambiar el proceso de control de cambios para que las actualizaciones de seguridad se tratasen como despliegues de producción con rollback y monitorización. La gran lección: las suposiciones son caras; los paquetes son baratos.
2) La optimización que salió mal: timeouts de inactividad agresivos
Un equipo de plataforma quiso reducir el número de conexiones en un gateway API interno. Bajaron el “idle timeout” drásticamente. Los gráficos se veían bien: menos conexiones abiertas, menos memoria, CPU ligeramente mejor. Se felicitaron en una reunión. Ese fue todo el postmortem, en sus cabezas.
Una semana después, los resets se dispararon—pero solo para unos pocos servicios. Esos servicios usaban HTTP keep-alive con conexiones de larga duración y ráfagas poco frecuentes. Los clientes se comportaban bien, reutilizando conexiones. El gateway ahora las estaba cerrando en los momentos de inactividad.
¿Qué pasó? El cliente enviaba la siguiente petición por una conexión que creía viva. El gateway ya había descartado el estado y la había reclamado. El gateway respondió con RST (o a veces el kernel lo hizo, según cómo se cerró el socket). Desde la perspectiva del cliente, “peer reset”. Desde la perspectiva del gateway, “te dije que dejaras de estar inactivo”.
Lo peor: los reintentos lo hicieron más ruidoso. Algunos clientes reintentaron de inmediato, multiplicando la carga. El cambio que buscaba reducir recursos terminó aumentando churn y latencia y añadiendo picos impredecibles de fallos.
La solución fue alinear timeouts de extremo a extremo: idle de keep-alive en cliente, idle del gateway, idle upstream, más jitter. También añadieron drenado explícito de conexiones para despliegues. La optimización está bien. La optimización sin pensar en modos de fallo es arte performativo.
3) La práctica aburrida pero correcta que salvó el día: correlación basada en tuplas
Una financiera tenía un entorno mixto: servidores Ubuntu, un LB gestionado y un service mesh en Kubernetes. Aparecían resets como 502 intermitentes, y los equipos ya estaban elaborando sus narrativas favoritas.
El responsable de guardia hizo algo aburrido: estandarizó la recolección de evidencia del incidente. Para cualquier reporte de reset, debías aportar: timestamp (UTC), IP cliente, IP servidor, puerto destino y si era posible el puerto efímero origen. Esa es la 5-tupla, menos el protocolo porque siempre era TCP.
Con eso, emparejaron una petición fallida única en: logs de cliente, logs del sidecar mesh, logs del balanceador y un tcpdump en un nodo. La captura probó que el RST se originó en el sidecar, no en el contenedor de la app ni en el upstream. El sidecar estaba aplicando una actualización de política que había invalidado brevemente una entrada SAN en una cadena de certificados durante la rotación.
La solución fue escalonar la rotación de certificados con solapamiento y validar con tráfico sintético antes de aplicar la política. La victoria mayor fue cultural: el formato de evidencia se volvió músculo. Sin heroicidades. Solo prueba repetible.
Errores comunes: síntomas → causa raíz → solución
Esta es la sección que hojeas mientras alguien pregunta “¿es culpa nuestra?” en una llamada.
1) Resets ocurren inmediatamente al conectar
- Síntoma: curl falla justo después de “Connected”, o el SYN recibe RST inmediatamente.
- Causa raíz: Nada escuchando en esa IP:puerto; VIP mal enrutado; firewall rechaza con tcp-reset.
- Solución: Verifica listener con
ss -ltnp; verifica configuración de VIP; revisa nftables por reglas reject; confirma que el servicio está ligado a la dirección correcta.
2) Resets se disparan durante despliegues
- Síntoma: ráfagas cortas de ECONNRESET durante rollout, luego normal.
- Causa raíz: conexiones en vuelo matadas por reinicio de proceso; sin shutdown/graceful; LB sigue enviando a pods que terminan.
- Solución: Añadir drenado de conexiones; aumentar terminationGracePeriod; marcar readiness=false antes de SIGTERM; alinear delay de desregistro del LB con el apagado de la app.
3) Solo fallan peticiones largas con resets
- Síntoma: endpoints pequeños bien; descargas/subidas grandes o llamadas lentas upstream se reinician a mitad de transmisión.
- Causa raíz: timeout de lectura del proxy; timeouts upstream; límites de buffer; dispositivos L7 abortan payloads grandes.
- Solución: Revisa timeouts del proxy; ajusta timeouts upstream; afina max body size; confirma si un DLP/WAF inyecta RST.
4) Resets parecen “aleatorios” en muchos servicios
- Síntoma: muchas apps no relacionadas ven resets al mismo tiempo.
- Causa raíz: dependencia compartida: agotamiento de conntrack en un NAT, LB sobrecargado, push de política de firewall, OOM del kernel en un nodo proxy compartido.
- Solución: Revisa conntrack count/max, salud del LB, logs de cambios de firewall y eventos OOM; estabiliza la capa compartida primero.
5) Resets afectan mayormente a un tipo de cliente (móvil, una región, un ISP)
- Síntoma: escritorio bien, móvil inestable; o una región falla.
- Causa raíz: diferencias de MTU/camino; comportamiento de NAT del operador; appliance de seguridad regional; enrutamiento geográfico a un POP roto.
- Solución: Compara capturas desde redes diferentes; ejecuta pings DF; revisa políticas de enrutamiento; verifica configuraciones de proxy por POP.
6) Resets upstream culpados a “la red” pero contadores del kernel muestran drops en listen
- Síntoma: Nginx informa upstream reset; el equipo upstream dice “no fuimos nosotros”.
- Causa raíz: overflow de cola de accept o presión en backlog SYN; proceso upstream demasiado lento para aceptar.
- Solución: Incrementa backlog; ajusta
net.core.somaxconn, backlog específico del servicio; arregla starvation de CPU; añade capacidad o rate limiting.
Listas de verificación / plan paso a paso
Checklist A: “Necesito una respuesta en 15 minutos”
- Reproduce con curl desde un host controlado; registra tiempo UTC exacto e IP/puerto destino.
- Identifica si hay un proxy/LB en el camino; determina dónde termina TLS.
- Captura paquetes RST en el proxy/LB (mejor) o en el servidor (siguiente mejor) durante 60–120 segundos mientras reproduces.
- Desde la captura, identifica la IP del emisor del RST. Si es la IP del cliente, es aborto por parte del cliente. Si es la VIP del proxy, es lado proxy. Si es la IP upstream, es upstream.
- Correlaciona con logs en el emisor: log de error del proxy, log de la app, journal del kernel, logs de firewall.
- Toma una decisión: mitigar (rollback, desactivar feature, evitar WAF, aumentar timeouts) mientras investigas causa raíz.
Checklist B: “Probar rigurosamente para que el equipo correcto lo arregle”
- Recopila evidencia de la 5-tupla desde al menos dos puntos de vista (proxy + upstream, o cliente + proxy).
- Captura ambas direcciones; no filtres solo “tcp-rst” sin contexto. Conserva el flujo completo de una conexión fallida.
- Confirma sincronización horaria (NTP) en todos los nodos implicados; la deriva de reloj arruina la correlación.
- Revisa contadores del kernel en el emisor sospechado (listen drops, aborts, retransmisiones).
- Verifica alineación de timeouts: timeout cliente, idle timeout del proxy, keep-alive upstream, timeouts del servidor.
- Revisa presión de recursos (CPU, memoria, descriptores de archivos) y reinicios forzados.
- Revisa reglas de firewall/nftables que puedan generar RSTs y cambios recientes.
- Documenta la prueba mínima: un extracto pcap + una línea de log + un cambio en un contador que apunten al mismo emisor.
Checklist C: “Prevenir la próxima ronda”
- Estandariza budgets de keep-alive e idle timeout a través de las capas; añade jitter.
- Implementa shutdown gracioso y drenado de conexiones para proxies y apps.
- Monitorea utilización de conntrack en nodos NAT/proxy.
- Expón contadores relacionados con resets: TcpExtListenDrops, aborts, nginx 499/502, estados de terminación de haproxy.
- Ejecuta probes sintéticos que registren fallos de handshake por separado de fallos de aplicación.
Preguntas frecuentes
1) ¿“Connection reset by peer” siempre significa que el servidor se cayó?
No. Significa que se recibió un RST TCP. El emisor puede ser el proceso servidor, el kernel del servidor, un proxy, un firewall o incluso el propio cliente.
2) ¿Cómo distingo reset de cliente vs reset de servidor en Nginx?
Nginx suele loguear abortos de cliente como “client prematurely closed connection” y abortos upstream como “recv() failed (104) while reading response header from upstream.” Combina eso con una captura de paquetes para estar seguro.
3) ¿Por qué veo resets solo durante el handshake TLS?
Causas comunes: problemas con el terminador TLS del proxy, enforcement de certificados/políticas por middleboxes, problemas MTU/PMTUD o un servicio que en realidad no escucha en el puerto esperado detrás de una VIP.
4) ¿Puede un firewall enviar un reset en vez de descartar?
Sí. Muchas reglas usan “reject with tcp reset” para fallar rápido. Eso es bueno para la experiencia de usuario cuando es intencional, y terrible cuando es accidental.
5) Capturé paquetes y veo RST desde la IP del proxy. ¿Eso prueba que el proxy es culpable?
Prueba que el proxy envió el RST. El proxy puede estar reaccionando a una falla upstream (timeouts, resets upstream) o aplicando una política (idle timeout, límites). Siguiente paso: verifica si el tráfico upstream muestra un error previo que disparó la decisión del proxy.
6) ¿Cuál es la diferencia entre un FIN y un RST?
FIN es un cierre educado: “ya no voy a enviar más.” RST es un aborto: “¡Detente inmediatamente; esta conexión es inválida!” FIN tiende a producir EOF limpio en las apps; RST tiende a producir ECONNRESET.
7) ¿Por qué aumentan los resets cuando reducimos timeouts de keep-alive?
Porque aumentas el churn de conexiones y la probabilidad de que un lado reutilice una conexión que el otro ya descartó. Los timeouts de inactividad desalineados son fábricas de resets.
8) ¿Puede Linux generar resets incluso si la aplicación está bien?
Sí. No hay listener, overflow de backlog, reglas de firewall reject locales y algunos cierres abruptos de socket pueden resultar en RSTs generados por el kernel.
9) Ejecutamos Kubernetes. ¿Quién es “el peer” ahora?
Potencialmente: la VIP del Service, kube-proxy/iptables/nft, un controlador Ingress, un proxy sidecar o el propio pod. Por eso capturas en el nodo y en la capa de ingress para encontrar el emisor real del RST.
10) Si no puedo ejecutar tcpdump en producción, ¿qué es lo siguiente mejor?
Logs del proxy más contadores del kernel más correlación estricta de tuplas/tiempos. Es evidencia más débil, pero aún puede probar direccionalidad. Empuja para capturas controladas y limitadas en tiempo como herramienta estándar de incidentes.
Conclusión: siguientes pasos que realmente reducen los resets
“Connection reset by peer” no es un diagnóstico. Es un síntoma con un artefacto físico muy específico: un RST TCP. Si puedes capturarlo, puedes dejar de adivinar. Si puedes identificar al emisor, puedes dejar de debatir.
Pasos prácticos siguientes:
- Adopta el guion rápido: reproducir, capturar RST, identificar emisor, correlacionar logs.
- Estandariza la evidencia: timestamp UTC + 5-tupla, cada vez. Hazlo aburrido.
- Alinea timeouts de extremo a extremo, y no “optimices” timeouts de inactividad sin probar la reutilización de conexiones de larga duración.
- Endurece capas compartidas: dimensionamiento de conntrack, límites de memoria de proxies, shutdown gracioso y revisión de reglas de firewall.
- Convierte esto en un runbook de guardia. Tu yo del futuro merece menos sorpresas.