Estás en medio de un cambio en producción. Ejecutas un comando más, esperas la salida… y el terminal se congela. Unos segundos después: “Broken pipe” o “Connection reset”. Tu sesión SSH no se “murió” aleatoriamente. Algo en la ruta decidió que tu flujo TCP silencioso parecía peso muerto.
Ubuntu 24.04 no tiene una maldición especial aquí, pero es lo suficientemente moderno (valores por defecto de OpenSSH, systemd, NAT omnipresente, firewalls agresivos) como para que consejos antiguos como “set TCPKeepAlive yes” a menudo fallen. Vamos a ser prácticos: qué revisar, qué cambiar y qué no tocar si quieres sesiones SSH que sobrevivan redes corporativas, balanceadores de carga en la nube y routers domésticos que fingen ser equipo de empresa.
Cómo mueren realmente las sesiones SSH
SSH es “solo TCP” con mucha criptografía y un poco de esperanza. Cuando tu sesión se cae, pasó una de estas cosas:
- Timeout de inactividad en la ruta: un firewall/NAT/load balancer decide que un flujo TCP inactivo es prescindible y olvida la asignación.
- Fluctuación de ruta: roaming por Wi‑Fi, renegociación de VPN, traspaso celular, un fallo del ISP. La conexión TCP no puede recuperarse.
- Un extremo se rinde: el cliente o el servidor decide que el par está muerto y cierra el socket.
- Terminación por recursos o políticas: sshd se reinicia, un host se reinicia, un agente de seguridad cierra sesiones largas, o políticas de PAM/systemd/logind terminan sesiones.
- Raridades de PMTU/fragmentación: paquetes quedan en negro, los keepalives no pasan, las retransmisiones se acumulan y entonces la aplicación expira.
“Keepalive” no es una sola característica. Es una familia de temporizadores y sondas en distintas capas:
- Keepalives del protocolo SSH (OpenSSH): mensajes cifrados a nivel de aplicación que viajan como tráfico real y pueden detectar un par muerto.
- Keepalives TCP (kernel): sondas TCP sin cifrar que pueden o no atravesar NATs/firewalls como esperas.
- Envejecimiento de sesiones en NAT/firewall: el dispositivo intermedio decide cuánto tiempo se permite estar “inactivo”.
Cuando alguien dice “SSH se desconecta aleatoriamente”, tradúzcalo a: “algo caduca mi flujo inactivo antes de lo que espero”. Tu trabajo es encontrar qué cosa y luego elegir la forma más barata y segura de mantener el flujo no inactivo o recuperarlo rápidamente.
Una comprobación más de realidad: los keepalives no sustituyen a un flujo de trabajo resiliente. Reducen el dolor. No hacen TCP inmortal.
Datos interesantes y contexto histórico (trivia útil)
- SSH surgió como respuesta a accesos remotos inseguros. El acceso remoto temprano usaba protocolos en texto plano; el auge de SSH fue tanto por saneamiento como por seguridad.
- OpenSSH no inventó los keepalives; los profesionalizó. El mecanismo de keepalive a nivel de protocolo es básicamente “envía algo legítimo y espera prueba de que el par está vivo”.
- Los valores por defecto de keepalive de TCP son famosamente poco útiles para humanos. Muchos sistemas históricamente usaban ~2 horas antes de sondear, ideal para sockets de BD de larga vida y terrible para una pausa por café.
- Los dispositivos NAT no son observadores neutrales. Mantienen estado por flujo y ese estado expira. El equipo de consumo suele ser más agresivo que firewalls empresariales.
- “Broken pipe” es tu shell informando SIGPIPE. El socket subyacente está muerto; tu siguiente escritura falla; tu app de terminal te lo dice en su propia forma dramática.
- Los firewalls con estado popularizaron los “idle timeouts” como válvula de seguridad. Rastrear cada flujo eternamente cuesta memoria; los timeouts son un recolector de basura rústico.
- Algunos balanceadores tienen timeouts distintos para TCP vs. HTTP. Si haces hairpin de SSH por un appliance optimizado para HTTP, puede tratar tu flujo TCP de larga vida como mobiliario sospechoso.
- El multiplexado de SSH (ControlMaster) puede ocultar caídas. Tus sesiones “nuevas” pueden ser simplemente canales nuevos en un socket TCP viejo que está a punto de morir.
- IPv6 cambia la historia de los middleboxes, pero no la elimina. Menos NAT no significa filtrado sin estado; las redes empresariales aún ponen timeouts a flujos inactivos.
Guía rápida de diagnóstico
Primero: decide si lo mató el servidor o la red
- En el cliente, reproduce con logging verboso y observa tiempos y quién inició el cierre.
- En el servidor, correlaciona los logs de sshd alrededor de la marca temporal del desconecte.
- Si no hay nada en los logs, asume que un middlebox descartó el estado y tu siguiente paquete chocó con un vacío.
Segundo: identifica “timeout por inactividad” vs “flap de ruta”
- Si muere después de una ventana de inactividad predecible (10m, 30m, 60m), eso es una política de timeout en algún lugar.
- Si muere durante movimiento (VPN on/off, roaming Wi‑Fi), eso es inestabilidad de la ruta; los keepalives no lo arreglan completamente, pero pueden acortar el tiempo de detección.
Tercero: elige la solución más barata
- Server-side ServerAlive* si solo necesitas que tus sesiones locales sean estables y no puedes cambiar los servidores.
- Server-side ClientAlive* si administras los servidores y quieres comportamiento consistente para todos.
- Keepalive del kernel TCP si también necesitas que otras aplicaciones no SSH sean estables, o tu entorno bloquea sondas a nivel SSH (raro pero real).
Cuarto: confirma el límite de timeout de la ruta
No adivines. Mide. Si la red mata flujos inactivos a ~900 segundos, enviar keepalives cada 60 segundos está bien; cada 5 segundos es espectáculo con carga extra.
Tareas prácticas: 12+ comprobaciones reales con comandos, significado de la salida, decisiones
Estas son deliberadamente “estilo operador”: ejecuta un comando, interprétalo, toma una decisión. Conserva marcas temporales. No confíes en sensaciones.
Task 1: Captura una sesión fallida con verbosidad de SSH
cr0x@server:~$ ssh -vvv ops@db-prod-1
OpenSSH_9.6p1 Ubuntu-3ubuntu13, OpenSSL 3.0.13 30 Jan 2024
debug1: Server host key: ssh-ed25519 SHA256:...
debug1: Authenticating to db-prod-1:22 as 'ops'
...
debug1: client_input_channel_req: channel 0 rtype keepalive@openssh.com reply 1
...
Write failed: Broken pipe
Qué significa: Si ves mensajes regulares keepalive@openssh.com y aún así se desconecta, tu intervalo de keepalive puede ser demasiado largo para la ruta, o la ruta está fluctuando (no solo envejecimiento por inactividad).
Decisión: Si las desconexiones ocurren sin logs de keepalive, habilita ServerAliveInterval en el cliente. Si ya hay keepalives pero las desconexiones persisten, reduce el intervalo e investiga eventos de red (VPN/Wi‑Fi) y logs del servidor.
Task 2: Comprueba si usas multiplexado SSH (y confías en un socket obsoleto)
cr0x@server:~$ ssh -G db-prod-1 | egrep -i 'controlmaster|controlpath|controlpersist'
controlmaster auto
controlpath /home/cr0x/.ssh/cm-%r@%h:%p
controlpersist 10m
Qué significa: Tus conexiones “nuevas” pueden reutilizar una conexión TCP subyacente. Si ese flujo TCP es silenciosamente matado, verás fallos extraños en varias pestañas.
Decisión: Para redes inestables, considera reducir ControlPersist o deshabilitar el multiplexado para ese host para hacer las fallas más visibles y reducir el radio de afectación.
Task 3: Ver razones de desconexión en el servidor (journalctl)
cr0x@server:~$ sudo journalctl -u ssh -S "2025-12-29 09:00" -U "2025-12-29 10:00" --no-pager | tail -n 20
Dec 29 09:41:12 db-prod-1 sshd[21877]: Connection closed by 10.20.4.18 port 53122 [preauth]
Dec 29 09:41:18 db-prod-1 sshd[21903]: Received disconnect from 10.20.4.18 port 53144:11: disconnected by user
Dec 29 09:52:07 db-prod-1 sshd[22011]: Timeout, client not responding from 10.20.4.18 port 53410
Dec 29 09:52:07 db-prod-1 sshd[22011]: Disconnecting: Timeout, client not responding
Qué significa: “Connection closed by” puede ser el cliente, o el cliente reaccionando a una ruta muerta. “Timeout, client not responding” apunta a keepalive del lado servidor (ClientAlive*) disparándose o a una ruta muerta del cliente.
Decisión: Si el servidor está poniendo timeouts a los clientes, ajusta ClientAliveInterval/ClientAliveCountMax. Si el cliente “cerró”, céntrate en middleboxes y ajustes del cliente.
Task 4: Verificar la configuración efectiva de sshd (no confíes en el archivo)
cr0x@server:~$ sudo sshd -T | egrep -i 'clientalive|tcpkeepalive|kex|loglevel'
clientaliveinterval 0
clientalivecountmax 3
tcpkeepalive yes
loglevel INFO
Qué significa: ClientAliveInterval 0 significa que el servidor no enviará keepalives a nivel SSH. Mucha gente asume que TCPKeepAlive yes es suficiente. A menudo no lo es.
Decisión: Si administras el servidor, configura ClientAliveInterval a un valor sensato (ejemplos más adelante). Si no, usa ServerAliveInterval en el cliente.
Task 5: Confirma la configuración efectiva del cliente SSH (incluye bloques Match)
cr0x@server:~$ ssh -G db-prod-1 | egrep -i 'serveralive|tcpkeepalive|ipqos'
serveraliveinterval 0
serveralivecountmax 3
tcpkeepalive yes
ipqos lowdelay throughput
Qué significa: No hay keepalive en el cliente habilitado. Si la red cierra flujos inactivos, estás viviendo peligrosamente.
Decisión: Añade ServerAliveInterval global o por host (y un ServerAliveCountMax sensato).
Task 6: Mide la ventana de fallo con una prueba de inactividad controlada
cr0x@server:~$ date; ssh -o ServerAliveInterval=0 -o ServerAliveCountMax=3 ops@db-prod-1 'echo connected; sleep 3600'
Mon Dec 29 10:02:01 UTC 2025
connected
Write failed: Broken pipe
Qué significa: Sin keepalives, la sesión muere durante la hora de sleep. Anota la marca de tiempo cuando muere; repite varias veces y normalmente verás una banda estrecha (por ejemplo, ~15 minutos).
Decisión: Si el corte es consistente, ajusta los keepalives para que se activen cómodamente dentro de esa ventana.
Task 7: Repite con keepalive agresivo de SSH para validar la hipótesis
cr0x@server:~$ date; ssh -o ServerAliveInterval=30 -o ServerAliveCountMax=3 ops@db-prod-1 'echo connected; sleep 3600; echo done'
Mon Dec 29 10:10:44 UTC 2025
connected
done
Qué significa: Si esto sobrevive, tu problema es casi seguro pérdida de estado por inactividad en la ruta de red.
Decisión: Establece un valor menos agresivo pero aún seguro (30 segundos a veces está bien; 60 segundos suele ser suficiente; 5 segundos suele ser teatral).
Task 8: Inspecciona los valores por defecto de keepalive TCP (servidor)
cr0x@server:~$ sysctl net.ipv4.tcp_keepalive_time net.ipv4.tcp_keepalive_intvl net.ipv4.tcp_keepalive_probes
net.ipv4.tcp_keepalive_time = 7200
net.ipv4.tcp_keepalive_intvl = 75
net.ipv4.tcp_keepalive_probes = 9
Qué significa: Primera sonda tras 2 horas. Eso no es “keepalive”; es arqueología.
Decisión: Si necesitas keepalives a nivel de kernel para múltiples servicios, reduce estos valores —pero hazlo conscientemente y documenta el alcance.
Task 9: Comprueba si la sesión TCP muestra retransmisiones o estancamientos (ss)
cr0x@server:~$ ss -tinp '( sport = :22 )' | head -n 12
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
ESTAB 0 0 10.10.8.21:22 10.20.4.18:53144 users:(("sshd",pid=21903,fd=4))
cubic wscale:7,7 rto:204 rtt:0.356/0.112 ato:40 mss:1448 pmtu:1500 rcvmss:1392 advmss:1448 cwnd:10 bytes_sent:104925 bytes_acked:104901 bytes_received:20631 segs_out:161 segs_in:149 send 325Mbit/s lastsnd:2800 lastrcv:2800 lastack:2800
Qué significa: lastsnd/lastrcv en milisegundos muestra cuánto hace que hubo tráfico. Si ves retrans en ascenso o rto inflándose, tienes un problema de calidad de ruta, no solo envejecimiento por inactividad.
Decisión: Si las retransmisiones se disparan cerca del desconecte, investiga MTU/VPN/Wi‑Fi. Los keepalives no salvarán una ruta con paquetes en negro.
Task 10: Buscar problemas de MTU/PMTU (ping con DF)
cr0x@server:~$ ping -M do -s 1472 -c 3 db-prod-1
PING db-prod-1 (10.10.8.21) 1472(1500) bytes of data.
1472 bytes from 10.10.8.21: icmp_seq=1 ttl=63 time=0.512 ms
1472 bytes from 10.10.8.21: icmp_seq=2 ttl=63 time=0.487 ms
1472 bytes from 10.10.8.21: icmp_seq=3 ttl=63 time=0.499 ms
--- db-prod-1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2040ms
Qué significa: Esto sugiere que una ruta de 1500 bytes funciona. Si esto falla pero tamaños menores sí funcionan, puede haber PMTU blackholing (común con algunas VPNs o túneles mal configurados).
Decisión: Si sospechas PMTU, arregla la red/MTU; no lo parchees con keepalives.
Task 11: Confirma si un firewall en el servidor está haciendo algo “útil”
cr0x@server:~$ sudo ufw status verbose
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), deny (routed)
New profiles: skip
To Action From
-- ------ ----
22/tcp ALLOW IN 10.20.0.0/16
Qué significa: UFW típicamente no va a dropear conexiones establecidas aleatoriamente, pero un logging pesado o reglas adicionales de nftables sí podrían. Esto es una comprobación de cordura, no una pistola humeante.
Decisión: Si ves límites por tasa, agotamiento de connection tracking o reglas “recent” agresivas, indaga en contadores de nftables y uso de conntrack.
Task 12: Revisa presión en conntrack (el agotamiento de la tabla de estado puede parecer caídas aleatorias)
cr0x@server:~$ sudo sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max
net.netfilter.nf_conntrack_count = 198432
net.netfilter.nf_conntrack_max = 262144
Qué significa: Si nf_conntrack_count está cerca de max, los flujos establecidos pueden ser expulsados o nuevas conexiones fallar. Síntomas SSH: caídas intermitentes o imposibilidad de reconectar.
Decisión: Si estás cerca del límite, arregla la carga (demasiados flujos de corta vida), aumenta el tamaño de la tabla o mueve el filtrado con estado a un dispositivo diseñado para ello.
Task 13: Comprueba si sshd se está reiniciando (y matando sesiones)
cr0x@server:~$ systemctl status ssh --no-pager
● ssh.service - OpenBSD Secure Shell server
Loaded: loaded (/usr/lib/systemd/system/ssh.service; enabled; preset: enabled)
Active: active (running) since Mon 2025-12-29 08:01:12 UTC; 2h 15min ago
Docs: man:sshd(8)
man:sshd_config(5)
Main PID: 1123 (sshd)
Tasks: 1 (limit: 18920)
Memory: 6.9M
CPU: 1.421s
CGroup: /system.slice/ssh.service
└─1123 "sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups"
Qué significa: Si el uptime es corto y se alinea con las caídas, estás persiguiendo lo equivocado: un reinicio mata sesiones. Gestión de configuración, actualizaciones desatendidas o un watchdog podrían estar involucrados.
Decisión: Si los reinicios se correlacionan, arregla el comportamiento de reinicio primero. Keepalive no superará un reinicio del demonio.
Task 14: Comprueba la versión de OpenSSH y la política de criptografía (raro, pero importa para renegociación)
cr0x@server:~$ ssh -V
OpenSSH_9.6p1 Ubuntu-3ubuntu13, OpenSSL 3.0.13 30 Jan 2024
Qué significa: Ubuntu 24.04 trae un OpenSSH moderno. Eso es bueno. También significa que supuestos de interoperabilidad con clientes/servidores antiguos pueden fallar en entornos con bordes raros.
Decisión: Si sólo clientes viejos se desconectan, prueba con clientes actualizados o ajusta algoritmos por host, pero no debilites la seguridad global para arreglar un equipo antiguo.
Task 15: Prueba que es “inactividad” generando tráfico de bajo impacto durante una sesión
cr0x@server:~$ ssh ops@db-prod-1 'while true; do date +"%T"; sleep 120; done'
10:31:02
10:33:02
10:35:02
Qué significa: Si esto permanece activo indefinidamente mientras tu sesión interactiva muere cuando dejas de escribir, has confirmado que es un problema de timeout por inactividad, no inestabilidad general.
Decisión: Arregla el comportamiento de inactividad con keepalives SSH o cambios en la política de red; no pierdas tiempo persiguiendo carga de CPU, a menos que los logs lo sugieran.
Perillas de keepalive que realmente importan (cliente, servidor, TCP)
Cliente: ServerAliveInterval y ServerAliveCountMax
Esta es la solución más efectiva y menos política porque solo requiere acceso a tu máquina. Envía un mensaje a nivel SSH por el canal cifrado periódicamente. Si el servidor (o la ruta) está muerto, el cliente lo notará tras algunas respuestas perdidas y desconectará. Eso es una característica: falla rápido en lugar de colgarte media hora.
Cómo funciona:
ServerAliveIntervalestablece cada cuánto (segundos) el cliente envía una petición de keepalive cuando no se ha recibido datos.ServerAliveCountMaxestablece cuántos keepalives sin respuesta antes de rendirse.
Para qué sirve:
- Mantener caliente el estado NAT/firewall (porque es tráfico real).
- Detectar sesiones muertas rápidamente y devolverte a un prompt desde el que reconectar.
Qué no es: No preservará tu estado de terminal a través de cambios de IP. Si te mueves entre redes, considera una herramienta diseñada para roaming (ver FAQ). Los keepalives de SSH son el cinturón de seguridad, no la teletransportación.
Servidor: ClientAliveInterval y ClientAliveCountMax
Si administras los servidores, los keepalives del lado servidor son sobre política e higiene. Le indicas al servidor que sondee clientes inactivos y expulse sesiones muertas. Ayuda con:
- Limpiar sesiones zombi cuando los clientes desaparecen detrás de redes rotas.
- Mantener el estado vivo a través de middleboxes (mismo beneficio que el lado cliente, solo que iniciado desde el otro extremo).
Hay una compensación: demasiado agresivo y matarás sesiones válidas en rutas de alta latencia o congestionadas temporalmente. Muy laxo, y vuelves a los colgamientos misteriosos.
Keepalives TCP del kernel: TCPKeepAlive y sysctls
OpenSSH tiene TCPKeepAlive (cliente y servidor). Cuando está habilitado, deja que el kernel envíe sondas TCP según los temporizadores del kernel.
Aquí está el truco: los valores por defecto de keepalive del kernel suelen ser demasiado lentos, y algunos NAT/firewalls tratan las sondas TCP diferente al tráfico real de aplicación. Además, los keepalives TCP pueden mantener un mapeo roto “medio vivo” el tiempo suficiente para confundirte. Los keepalives a nivel SSH suelen ser más claros y ajustables por host.
Cuándo recomiendo realmente ajustar keepalives del kernel:
- Tienes múltiples servicios TCP de larga vida (no solo SSH) que sufren las mismas caídas por inactividad.
- No puedes confiar en que la configuración SSH se aplique consistentemente (flota de clientes, portátiles no gestionados).
- Estandarizas comportamiento en un entorno controlado (servidores, bastiones, jump hosts).
Economía de timeouts: elige intervalos según el middlebox más débil
Si un firewall pone timeout a TCP inactivo en 10 minutos, un keepalive a 5 minutos funciona. Uno a 30 segundos también funciona, pero es cháchara innecesaria. Tu objetivo es “cómodamente por debajo del timeout más corto que no controlas”.
Regla mnemotécnica práctica: 30–60 segundos suele ser seguro para redes hostiles. 120 segundos está bien en centros de datos bien gestionados. Cualquier cosa por debajo de 15 segundos suele indicar que estás depurando por superstición.
Chiste #1: Si tu intervalo de keepalive SSH es 1 segundo, no estás manteniendo la sesión viva: le estás dando trabajo al firewall.
Una cita para mantenerte honesto
Idea parafraseada (Werner Vogels, contexto de fiabilidad/operaciones): “Todo falla; diseña asumiendo que la falla es normal.”
Ese es el modelo mental correcto. Los keepalives no previenen fallos; hacen que los fallos sean previsibles y detectables.
Ajustes recomendados y opinados para Ubuntu 24.04
Si quieres una respuesta única que funcione para la mayoría de personas en la mayoría de redes: habilita keepalives a nivel SSH en el cliente, y opcionalmente en el servidor para limpieza. Mantén TCP keepalives activados pero no dependas de ellos.
Cliente: establece keepalive en ~/.ssh/config
Usa un valor por defecto global y sobreescribe para redes frágiles o enlaces de alta latencia.
cr0x@server:~$ cat > ~/.ssh/config <<'EOF'
Host *
ServerAliveInterval 60
ServerAliveCountMax 3
TCPKeepAlive yes
Host *.corp
ServerAliveInterval 30
ServerAliveCountMax 3
EOF
Qué significa: Cada minuto el cliente envía un keepalive; si falta la respuesta 3 veces (unos 3 minutos), se desconecta. En hosts corporativos, es cada 30 segundos.
Decisión: Si aún ves caídas alrededor de un timeout conocido (p. ej., 5 minutos), reduce el intervalo a 20–30 segundos para ese entorno. Si ves caídas durante viajes/roaming, deja de intentar forzarlo con keepalives y usa un enfoque amigable con roaming (FAQ).
Servidor: establece keepalive en /etc/ssh/sshd_config.d/
En Ubuntu 24.04, prefiere archivos drop-in en lugar de editar el monolito. Manténlo legible y reversible.
cr0x@server:~$ sudo tee /etc/ssh/sshd_config.d/50-keepalive.conf > /dev/null <<'EOF'
ClientAliveInterval 60
ClientAliveCountMax 3
TCPKeepAlive yes
EOF
cr0x@server:~$ sudo sshd -t
cr0x@server:~$ sudo systemctl restart ssh
cr0x@server:~$ sudo systemctl status ssh --no-pager | head -n 8
● ssh.service - OpenBSD Secure Shell server
Loaded: loaded (/usr/lib/systemd/system/ssh.service; enabled; preset: enabled)
Active: active (running) since Mon 2025-12-29 11:02:12 UTC; 4s ago
Docs: man:sshd(8)
man:sshd_config(5)
Qué significa: El servidor sondeará clientes inactivos. Si el cliente no puede responder en ~3 minutos, sshd cierra la sesión. Eso suele ser lo deseado para conexiones muertas.
Decisión: Si tienes enlaces de alta latencia o VPNs intermitentes, aumenta ClientAliveCountMax o el intervalo para evitar falsos positivos.
Ajuste del kernel TCP keepalive (usar con moderación)
Si debes ajustar valores por defecto del kernel, hazlo explícita y persistentemente:
cr0x@server:~$ sudo tee /etc/sysctl.d/60-tcp-keepalive.conf > /dev/null <<'EOF'
net.ipv4.tcp_keepalive_time = 300
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 5
EOF
cr0x@server:~$ sudo sysctl --system | tail -n 6
* Applying /etc/sysctl.d/60-tcp-keepalive.conf ...
net.ipv4.tcp_keepalive_time = 300
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 5
Qué significa: La sonda empieza tras 5 minutos de inactividad, luego cada 30 segundos, hasta 5 sondas. Eso son ~7.5 minutos para declarar muerto al nivel TCP.
Decisión: Hazlo solo si entiendes el impacto en todas las conexiones TCP de ese host. En sistemas muy ocupado puede aumentar notablemente el tráfico de keepalives (aun así suele ser pequeño, pero “pequeño” escala).
Qué evitar
- No configures intervalos de keepalive a 5 segundos globalmente. Generarás tráfico continuo de fondo, mantendrás estado caliente en cada middlebox y aun así fallarás durante cambios reales de ruta.
- No confíes solo en
TCPKeepAlive yescon los sysctls por defecto. Dos horas no es una estrategia de keepalive; es un cuento para dormir. - No “arregles” caídas deshabilitando la renegociación de cifrado ni debilitando la criptografía. La estabilidad de la sesión no es excusa para retroceder en seguridad.
Tres micro-historias corporativas desde el terreno
1) El incidente causado por una suposición equivocada: “El firewall es stateful, así que nos recordará”
Un equipo operaba hosts de salto para que ingenieros accedieran a bases de datos en producción. Las sesiones se “desconectaban al azar” —sobre todo durante la respuesta a incidentes, claro. La gente culpó a los jump hosts. Alguien incluso parcheó el kernel “por si acaso”, que es una manera impresionante de perder tiempo.
La suposición equivocada era silenciosa y letal: “Firewall con estado significa que mantiene el estado hasta que la conexión cierre”. En la práctica, el firewall tenía un timeout de inactividad para flujos TCP establecidos. Si el canal SSH estaba quieto—leyendo logs en otra pestaña, pensando, esperando—las asignaciones envejecían.
Intentaron aumentar límites del servidor, subir descriptores de archivo, ajustar logging de sshd. Nada cambió. La pista clave fue el patrón en reloj: desconexiones agrupadas alrededor de una ventana de inactividad consistente. Cuando ejecutaron una prueba controlada sleep 3600 con y sin ServerAliveInterval, quedó embarazosamente claro.
La solución no fue heroica: poner ServerAliveInterval 30 en los hosts de salto (y recomendarlo en portátiles), además de ClientAliveInterval 60 en servidores para limpiar zombis. El postmortem del incidente fue corto y algo humillante, que es lo mejor.
2) La optimización que salió mal: multiplexado en todas partes
Otra organización estandarizó el multiplexado SSH porque acelera conexiones repetidas. ControlMaster + ControlPersist es genial cuando ejecutas muchos comandos cortos (automatización, comprobaciones de flota). La ganancia de rendimiento es real.
Luego el equipo de VPN lanzó un cliente de túnel “inteligente” que renegociaba rutas periódicamente. La conexión TCP subyacente se quedaba atascada o huérfana. Con multiplexado, los ingenieros no veían “la conexión se cayó” inmediatamente. Veían fallos aleatorios: scp colgado, nuevas sesiones ssh fallando al instante, terminales que aceptaban entrada pero no mostraban salida.
La optimización convirtió un socket fallido en una dependencia compartida. Un control socket muerto podía romper diez pestañas. El modo de fallo se percibía caótico porque la gente abría “nuevas sesiones” que en realidad no eran nuevas.
La solución fue matizada: mantener multiplexado para automatización y redes internas estables, pero deshabilitarlo (o acortar ControlPersist) para hosts accedidos por VPNs inestables. También ajustaron ServerAliveInterval para que el socket de control muriera rápido cuando la VPN fallara, permitiendo crear un socket nuevo al conectar de nuevo.
3) La práctica aburrida pero correcta que salvó el día: estándares explícitos de keepalive
Una firma financiera tenía una política: todos los bastiones y estaciones administrativas incluyen una configuración SSH base con keepalives, y los servidores aplican una política coincidente. No era glamuroso. Estaba escrito. Se desplegó consistentemente.
Un día, un cambio de red introdujo un timeout de inactividad menor en un conjunto de firewalls de segmentación. La caída fue casi nula. Los ingenieros notaron algunas sesiones que se cayeron más rápido que antes (porque la detección de keepalive era más rápida), pero la rabia por desconexiones “aleatorias” no apareció.
Lo que los salvó fue la alineación aburrida: el cliente envía keepalives cada 60 segundos; el servidor expulsa clientes muertos en unos minutos; los sysctls de TCP estaban en valores razonables en los bastiones. Cuando la red se puso más estricta, sus sesiones seguían generando tráfico periódico suficiente para permanecer “no inactivas”.
Chiste #2: El sistema más fiable es el que puedes explicarle a un auditor sin llorar.
Errores comunes (síntoma → causa raíz → solución)
1) Síntoma: “Broken pipe” tras ~10–30 minutos de inactividad
Causa raíz: Timeout por inactividad en NAT/firewall/load balancer. La asignación expira; el siguiente paquete recibe reset o se pierde en negro.
Solución: Configura ServerAliveInterval a 30–60 y ServerAliveCountMax a 3. Si administras servidores también, configura ClientAliveInterval de forma similar.
2) Síntoma: Sesión se cuelga (sin salida), luego finalmente se desconecta
Causa raíz: Ruta en negro o caída asimétrica; TCP no sabe inmediatamente que el par es inalcanzable. No hay tráfico de aplicación que revele la falla.
Solución: Habilita keepalives SSH para que detecte la falta de respuesta y cierre la sesión. Si se correlaciona con movimiento de VPN/Wi‑Fi, trátalo como inestabilidad de ruta y considera una herramienta de roaming.
3) Síntoma: Múltiples terminales mueren juntos; nuevos comandos SSH fallan al instante
Causa raíz: Multiplexado ControlMaster reutilizando un socket de control muerto, o una conexión TCP subyacente compartida por muchas sesiones.
Solución: Reduce ControlPersist, deshabilita multiplexado para ese host y asegúrate de que los keepalives estén habilitados para que el socket de control falle rápido.
4) Síntoma: Logs del servidor muestran “Timeout, client not responding”
Causa raíz: ClientAliveInterval del servidor está habilitado y disparando, o la ruta de red bloquea respuestas el tiempo suficiente para alcanzar el umbral.
Solución: Mantén ClientAliveInterval pero ajusta un ClientAliveCountMax realista (a menudo 3). Si los clientes están en enlaces de alta latencia, incrementa el contador o el intervalo.
5) Síntoma: Caídas coinciden con reinicios de sshd o actualizaciones desatendidas
Causa raíz: Reinicios del servicio matan sesiones. Esto no es un problema de keepalive.
Solución: Controla la cadencia de reinicios, usa ventanas de mantenimiento o mantiene bastiones estables. Verifica con journalctl y systemctl status.
6) Síntoma: Sólo fallan salidas grandes (o scp); la escritura interactiva funciona
Causa raíz: PMTU/fragmentación o MTU rota en el túnel. Paquetes pequeños pasan; los grandes se pierden.
Solución: Valida con pings con DF y arregla la MTU. Los keepalives no arreglan agujeros de paquetes.
7) Síntoma: Reconexiones fallan intermitentemente; a veces SSH no puede establecerse
Causa raíz: Agotamiento de la tabla conntrack en un firewall o host; o reglas de rate-limiting.
Solución: Revisa nf_conntrack_count, inspecciona políticas de firewall, ajusta tamaño de conntrack y reduce patrones de tráfico abusivos en otros lugares.
8) Síntoma: Funciona desde una red, falla desde otra
Causa raíz: Políticas de middlebox diferentes: firewall corporativo, portal cautivo de hotel, CGNAT del ISP.
Solución: Usa bloques de configuración SSH por host o por red. Si es necesario, considera un transporte alternativo (VPN/bastion) en lugar de pelear con cada red cautiva.
Listas de verificación / plan paso a paso
Paso a paso: estabiliza tus propias sesiones SSH (solo cliente)
-
Mide la ventana de fallo. Ejecuta una prueba inactiva sin keepalive y observa cuándo se cae.
cr0x@server:~$ ssh -o ServerAliveInterval=0 ops@db-prod-1 'echo connected; sleep 1800' connected Write failed: Broken pipeDecisión: Si cae en un tiempo consistente, estás ante un timeout por inactividad.
-
Valida con un keepalive temporal.
cr0x@server:~$ ssh -o ServerAliveInterval=30 -o ServerAliveCountMax=3 ops@db-prod-1 'echo connected; sleep 1800; echo survived' connected survivedDecisión: Si sobrevive, incorpóralo en
~/.ssh/config. -
Aplica la configuración globalmente, sobreescribe donde haga falta.
cr0x@server:~$ ssh -G db-prod-1 | egrep -i 'serveraliveinterval|serveralivecountmax' serveraliveinterval 60 serveralivecountmax 3Decisión: Confirma que los valores efectivos son los que querías (los bloques Match pueden sorprenderte).
-
Si te desplazas entre redes, deja de esperar que TCP sobreviva a cambios de IP. Usa una herramienta diseñada para roaming o acepta reconexiones y usa tmux/screen en el servidor (FAQ).
Paso a paso: estandarizar comportamiento en servidores (enfoque de flota)
-
Comprueba la configuración efectiva de sshd.
cr0x@server:~$ sudo sshd -T | egrep -i 'clientalive|tcpkeepalive' clientaliveinterval 0 clientalivecountmax 3 tcpkeepalive yesDecisión: Si
clientaliveintervales 0, no estás sondando clientes inactivos. -
Añade una política keepalive como drop-in.
cr0x@server:~$ sudo tee /etc/ssh/sshd_config.d/50-keepalive.conf > /dev/null <<'EOF' ClientAliveInterval 60 ClientAliveCountMax 3 TCPKeepAlive yes EOF -
Valida y reinicia.
cr0x@server:~$ sudo sshd -t cr0x@server:~$ sudo systemctl restart sshDecisión: Si
sshd -tfalla, no reinicies; corrige la sintaxis primero. -
Observa cambios en los logs tras el despliegue.
cr0x@server:~$ sudo journalctl -u ssh -S "now-1h" --no-pager | egrep -i 'timeout|disconnect|closed' | tail -n 20 Dec 29 11:21:07 db-prod-1 sshd[24102]: Timeout, client not responding from 10.20.4.18 port 54119 Dec 29 11:21:07 db-prod-1 sshd[24102]: Disconnecting: Timeout, client not respondingDecisión: Si los timeouts aumentan inesperadamente, tu intervalo/contador puede ser demasiado agresivo para tu entorno.
Paso a paso: decide si ajustar keepalive TCP del kernel
- Comprueba los sysctls actuales (
tcp_keepalive_timeespecialmente). - Haz inventario de quién comparte el host. Bajar keepalives afecta a todos los sockets TCP (bases de datos, agentes, exporters).
- Cámbialo vía /etc/sysctl.d/ y mide tráfico/comportamiento.
- Revierte rápidamente si ves efectos secundarios inesperados (algunas aplicaciones ya implementan sus propios keepalives).
Preguntas frecuentes (FAQ)
1) ¿Debería usar ServerAliveInterval o TCPKeepAlive?
Usa ServerAliveInterval primero. Es a nivel SSH, configurable por host y tiende a mantener el estado NAT de forma fiable. Mantén TCPKeepAlive yes pero no confíes en los valores por defecto del kernel.
2) ¿Qué valores debo poner para los keepalives?
Empieza con ServerAliveInterval 60 y ServerAliveCountMax 3 en clientes. Para redes más estrictas, usa 30 segundos. En servidores, ClientAliveInterval 60 y ClientAliveCountMax 3 es una base sensata.
3) ¿Por qué mi sesión muere incluso mientras escribo?
Eso usualmente no es timeout por inactividad. Busca pérdida de paquetes, churn de rutas VPN, roaming Wi‑Fi o problemas de MTU. Revisa ss -tinp por retransmisiones y ejecuta pings con DF para probar MTU.
4) ¿ClientAliveInterval expulsa a los usuarios?
PUEDE hacerlo si está configurado demasiado agresivamente. Está pensado para eliminar sesiones muertas, no para castigar enlaces lentos. Si ves desconexiones falsas, aumenta ClientAliveCountMax o el intervalo.
5) ¿Los keepalives son malos para la seguridad?
No inherentemente. Envían tráfico autenticado mínimo. La verdadera pregunta de seguridad es si mantienes sesiones vivas que deberían cerrarse por políticas. Si tu organización exige logout por inactividad, los keepalives pueden entrar en conflicto con esa intención.
6) Estoy detrás de un proxy/firewall corporativo que mata SSH. ¿Servirán los keepalives?
Sólo si las conexiones SSH están permitidas pero los flujos inactivos se envejecen. Si la red bloquea activamente SSH o realiza inspección profunda que termina sesiones de larga vida, puede que necesites un bastión o VPN sancionado.
7) ¿Debo ajustar net.ipv4.tcp_keepalive_* en Linux Ubuntu 24.04?
Sólo si tienes una necesidad a nivel de sistema. Para SSH solo, prefiere los keepalives de OpenSSH. Si ajustas keepalives del kernel, documéntalo y entiende que afecta a todas las conexiones TCP.
8) Mi sesión SSH se cae cuando cierro la tapa del portátil. ¿Tiene que ver con keepalive?
No realmente. Es probable que tu portátil suspenda la red; la conexión TCP queda obsoleta. Los keepalives pueden ayudar a detectar la sesión obsoleta más rápido, pero no pueden mantener un NIC suspendido comunicándose.
9) ¿Y usar tmux o screen?
Hazlo. Los multiplexores de terminal en el servidor no evitan desconexiones, pero previenen pérdida de trabajo. Combina tmux con keepalives y tendrás menos caídas y menos dolor cuando ocurran.
10) ¿Hay una herramienta mejor que SSH para redes en roaming?
Sí: existen herramientas de sesión diseñadas para sobrevivir a cambios de IP y conectividad intermitente. Los keepalives ayudan a SSH, pero no cambian los fundamentos de TCP.
Próximos pasos que puedes hacer hoy
Aquí está el camino profesional de producción fuera de “SSH se desconecta aleatoriamente”:
- Prueba el modo de falla. Mide si es timeout por inactividad o inestabilidad de ruta usando una prueba
sleepy logs verbosos. - Habilita keepalives SSH en el cliente. Pon
ServerAliveInterval 60(30en redes hostiles) yServerAliveCountMax 3. - Si administras servidores, añade keepalives del lado servidor. Usa un drop-in con
ClientAliveInterval 60yClientAliveCountMax 3para limpiar sesiones muertas. - Solo entonces considera ajustar keepalives del kernel. Tiene impacto más amplio, a veces es necesario, a menudo se abusa.
- Reduce las sorpresas. Si el multiplexado convierte un socket muerto en muchas sesiones rotas, delimítalo con cuidado.
Si haces eso en orden, dejarás de tratar las desconexiones SSH como el clima y empezarás a tratarlas como lo que son: una política de timeout encontrándose con un flujo TCP inactivo. Arreglable. Predecible. A veces aún molesto—por las redes—pero al menos ya no misterioso.