usar Claude Code en VPS con tmux
Evite que la sesión de Claude Code muera por SIGHUP al cerrar SSH. Aprenda a ejecutar la CLI en un VPS Linux usando tmux para mantener procesos activos.
El problema es la tapa de la laptop, no la CLI
Claude Code funciona correctamente en su laptop hasta que la cierra: la sesión SSH se interrumpe, el shell recibe un SIGHUP y el agente muere tres minutos después de iniciar una ejecución de pruebas. Ejecute la CLI en una máquina que no entre en suspensión, dentro de un multiplexor de terminal cuyos procesos no sean hijos de su sesión SSH. Ese es el método clave; el componente esencial es tmux, no la instalación.
Esta página trata sobre la gestión de un servidor donde se ejecutan agentes. Si no dispone de un servidor Linux que pueda mantener encendido, nada de esto es aplicable. Ese es el único requisito indispensable.
Qué hace tmux realmente
Al iniciar una sesión SSH, sshd crea un proceso hijo de la shell y le asigna un pseudo-terminal; todo lo que se ejecute desde esa shell será un proceso hijo de esta. Si se pierde la conexión, el kernel destruye el pty, la shell recibe una señal SIGHUP y finaliza también sus procesos hijos. Los procesos en primer plano de larga duración mueren.
tmux invierte la jerarquía de propiedad. El comando tmux que ejecutas es un cliente ligero que se comunica mediante un unix socket con un tmux server que se ejecuta de forma independiente a tu terminal. Las shells dentro de una sesión son procesos hijos de dicho servidor, no de sshd. Si se corta la conexión SSH, el cliente se cierra pero el servidor, la sesión y el agente continúan ejecutándose. Al reconectar con tmux attach, regresas a la misma shell con el mismo historial de scrollback. nohup también sobrevive a la pérdida de conexión, pero no permite el reingreso; no es posible re-adjuntar a una TUI en segundo plano. Claude Code es interactivo; tmux (o screen) es la herramienta adecuada.
Dimensionamiento del equipo
La CLI es un proceso de Node; no es lo que consume los recursos de la máquina. Lo que consume los recursos es cualquier proceso que el agente ejecute por usted: una compilación, una suite de pruebas completa, tsc, un language server o una base de datos en Docker. Dimensione el equipo para el toolchain, no para la CLI. Añada swap aunque no planee usarla; esto convierte un error OOM (Out of Memory) crítico en una compilación lenta:
sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstabMonitoree también el disco: los repositorios, node_modules y las imágenes de Docker se acumulan rápidamente. Si el toolchain utiliza más allá de los contenedores, como máquinas virtuales completas (un guest de KVM o un nodo local de Kubernetes), verifique que el plan incluya las extensiones de virtualización de CPU antes de contratarlo. Ejecutar virtualización anidada en un VPS es una función que el proveedor debe habilitar, no algo que se pueda activar desde el interior del guest.
Un usuario sin privilegios de root primero
Cree un usuario dedicado con su propio directorio home y coloque su clave pública en su lugar:
sudo adduser --disabled-password --gecos "" agent
sudo install -d -m 700 -o agent -g agent /home/agent/.ssh
sudo cp ~/.ssh/authorized_keys /home/agent/.ssh/authorized_keys
sudo chown agent:agent /home/agent/.ssh/authorized_keys
sudo chmod 600 /home/agent/.ssh/authorized_keysDeliberadamente, agent no pertenece al grupo sudo. Si necesita un paquete del sistema, instálelo. Esa decisión elimina la mayoría de las formas en que un comando de shell erróneo puede dañar el host.
Higiene de SSH para un servidor siempre encendido
La autenticación por contraseña en una máquina expuesta a internet que contiene un agente y su código fuente es un riesgo innecesario. Desactívela. En Ubuntu 24.04 y Debian 13, /etc/ssh/sshd_config incluye /etc/ssh/sshd_config.d/*.conf, por lo que es mejor crear un archivo nuevo en lugar de editar la configuración principal:
# /etc/ssh/sshd_config.d/10-hardening.conf
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin noValide y recargue — mantenga su sesión actual abierta mientras prueba una nueva desde una segunda terminal:
sudo sshd -t && sudo systemctl restart sshUn detalle en Ubuntu 24.04: sshd se activa mediante sockets. Los ajustes de autenticación se aplican en systemctl restart ssh, pero cualquier cambio en el puerto de escucha Port requiere systemctl daemon-reload y reiniciar ssh.socket.
Luego el firewall. Permita SSH antes de activarlo o perderá el acceso al servidor:
sudo ufw allow OpenSSH
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw enableInstale fail2ban sabiendo exactamente qué beneficio aporta: una vez desactivada la autenticación por contraseña, los ataques de fuerza bruta no funcionarán; esto evita que los intentos fallidos llenen el journal.
# /etc/fail2ban/jail.local
[sshd]
enabled = true
backend = systemd
maxretry = 5
bantime = 1hFinalmente, aplique parches automáticamente con sudo apt install unattended-upgrades y sudo dpkg-reconfigure -plow unattended-upgrades. Tenga en cuenta la interacción con tmux: si activa Unattended-Upgrade::Automatic-Reboot y una actualización del kernel reinicia el equipo, todas las sesiones se cerrarán. Si lo deja desactivado, reinicie manualmente cuando no haya procesos en ejecución.
Instalar Node.js y Claude Code en Ubuntu
Claude Code es una CLI de Node, por lo que requiere una versión reciente de Node. Los paquetes de la distribución suelen estar desactualizados; el método habitual en Ubuntu y Debian es usar NodeSource, que ofrece un repositorio firmado (sin apt-key — esa herramienta ya no existe):
curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash -
sudo apt install -y nodejs
node --versionError común: instale la CLI con su usuario agent, nunca con sudo npm -g. Un prefijo global propiedad de root genera errores de permisos posteriormente y deja archivos de root en el cache de npm. Configure el prefijo de npm en el home del usuario primero:
mkdir -p ~/.npm-global
npm config set prefix ~/.npm-global
echo 'export PATH="$HOME/.npm-global/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
npm install -g @anthropic-ai/claude-code
claude --versionLa exportación debe ir en ~/.bashrc, no en ~/.profile, y debe colocarse antes de la cláusula "If not running interactively, don't do anything" al principio del archivo: tmux puede iniciar shells sin login, los cuales leen ~/.bashrc y omiten ~/.profile — ~/.profile solo se ejecuta en shells de login. Usar un Node por usuario mediante un gestor de versiones como nvm logra el mismo resultado; el objetivo es que npm install -g nunca requiera sudo. npm sigue funcionando correctamente, o puede usar el script de instalación nativo de Anthropic, que es el estándar documentado actualmente. Revise la documentación de instalación de Anthropic antes de copiar comandos; los métodos de instalación cambian.
Ejecute claude dentro de un repositorio para iniciar la herramienta. La primera ejecución guía el proceso de autenticación; un servidor sin interfaz gráfica no tiene navegador, por lo que el flujo le entregará una URL para abrir en su propia máquina y un código para introducir en la terminal. (Otra opción es usar una API key en las variables de entorno). En ambos casos, la credencial se guardará en el servidor — lo que nos lleva a la parte que la mayoría omite.
La conversación sobre el radio de explosión
Un agente con acceso a la shell es una shell. Puede leer cualquier archivo que el usuario que lo ejecuta pueda leer, y puede realizar push en cualquier lugar donde ese usuario tenga permisos. Esto no es una crítica a la herramienta, es su definición; por ello, la cuenta bajo la cual se ejecuta es más importante que cualquier configuración individual.
- Usuario dedicado y sin privilegios. Sin grupo
sudo, sin directorio home compartido con su propia cuenta. - Sin credenciales de producción en el equipo. Sin
~/.aws/credentialsque contenga llaves de producción, sin.envcopiadas desde producción, ni contraseñas de base de datos con acceso de escritura a recursos críticos. Asigne al agente una credencial de staging o de solo lectura. - Tokens con alcance limitado. Un token de GitHub de grano fino limitado a un solo repositorio; una deploy key cuando el acceso de lectura sea suficiente.
Claude Code incluye un flag que omite completamente sus avisos de permisos. En un portátil o en un proyecto temporal, la decisión es suya. En un servidor que contiene tokens, este flag elimina la última barrera entre una instrucción mal interpretada y un git push --force. Lo que el flag cambia realmente, y cómo contener un agente que se ejecute con él —desde el sandbox integrado hasta una VPS desechable— se detalla en ejecutar Claude Code de forma segura en un servidor.
Deploy key vs SSH agent forwarding
Es tentador usar ssh -A para que git pueda usar la llave en su portátil. Entienda qué otorga esto: el agent forwarding expone el socket de su agente SSH local a los procesos que se ejecuten como ese usuario en el equipo. Cualquier proceso ejecutado como agent —incluido el agente— puede solicitar a su llave que firme para cualquier host al que tenga acceso, siempre que usted permanezca conectado. Esto es mucho más que "permitir que git haga pull de este único repositorio".
En su lugar, genere una llave en el servidor, regístrela como una deploy key por repositorio (solo con acceso de escritura si el agente necesita hacer push) y configure una identidad de git para que los commits desde el equipo sean reconocibles:
ssh-keygen -t ed25519 -C "agent deploy key" -f ~/.ssh/id_ed25519_repo
cat ~/.ssh/id_ed25519_repo.pub # paste into the repo's Deploy Keys
git config --global user.name "Agent (build box)"
git config --global user.email "agent@example.com"El flujo de trabajo de tmux
Instálelo (sudo apt install tmux) y luego cree un ~/.tmux.conf mínimo:
set -g mouse on
set -g history-limit 50000
set -g default-terminal "tmux-256color"Cuatro comandos cubren el uso diario:
tmux new -A -s claude # attach to session "claude", creating it if absent
# ...run `claude` inside it, work normally...
# Ctrl-b then d -> detach; everything keeps running
tmux ls # list sessions
tmux attach -t claude # reattach, from this machine or any other
tmux kill-session -t claudetmux new -A -s claude es el comando que debe memorizar: se conecta si la sesión existe y la crea si no existe. Un solo comando sirve para iniciar la jornada o retomar el trabajo tras una desconexión. Cree un alias para este comando. Dentro de una sesión, Ctrl-b c abre una ventana, Ctrl-b n y Ctrl-b p permiten alternar entre ellas, y Ctrl-b [ activa el modo de copia para desplazarse hacia atrás (q lo cierra).
Un detalle sobre las sesiones que no se cierran: el agente reenvía toda la conversación en cada turno. Lea en qué gastan sus tokens las sesiones de Claude Code de larga duración antes de dejar una sesión ejecutándose durante una semana.
Modos de fallo
"My session is gone." tmux ls imprime no server running on /tmp/tmux-1000/default. Esto casi siempre significa que el proceso nunca estuvo dentro de tmux — iniciaste sesión por SSH, ejecutaste claude directamente y la desconexión lo detuvo. No hay nada que recuperar. El hábito para evitarlo: tmux new -A -s <project> es el primer comando después de cada inicio de sesión.
El panel se reduce a un cuadro diminuto. tmux ajusta el tamaño de una sesión al cliente conectado más pequeño; un cliente inactivo conectado desde otra máquina comprime la pantalla. Fuerza la desconexión de los demás al conectarte: tmux attach -d -t claude.
Un build imprime Killed. Una sola palabra, sin traza de la pila (stack trace). Confirma con sudo dmesg -T | grep -i -E 'out of memory|killed process' — el OOM killer del kernel seleccionó el proceso más grande. En Node, podrías ver en su lugar FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory. Soluciones, en orden: añadir swap (arriba), limitar las pruebas y el paralelismo del compilador, aumentar el heap de Node con NODE_OPTIONS=--max-old-space-size=..., o aumentar el tamaño del VPS. El OOM killer también puede seleccionar el tmux server en lugar del build, terminando con tu sesión; si systemd-oomd se está ejecutando, puede matar un slice de usuario completo con el mismo efecto.
npm error code EACCES / permission denied, mkdir '/usr/lib/node_modules/...'. Una instalación global en un prefijo propiedad de root. Usa el prefijo ~/.npm-global mencionado arriba. Si ya ejecutaste sudo npm en algún momento, también podrías ver Your cache folder contains root-owned files — repara con sudo chown -R $(id -u):$(id -g) ~/.npm.
claude: command not found — pero solo a veces. Tu exportación PATH se encuentra en ~/.bashrc debajo de la guarda "If not running interactively, don't do anything", por lo que los shells no interactivos la omiten. Mueve la exportación arriba de esa guarda y mantenla en ~/.bashrc, no en ~/.profile: tmux puede iniciar shells que no son de login, los cuales leen ~/.bashrc y nunca tocan ~/.profile.
Colores distorsionados tras conectar. Un desajuste de TERM — la línea default-terminal de arriba es la solución.
Las sesiones desaparecen tras un reinicio. No es un error: el tmux server es un proceso, y un reinicio lo finaliza. Revisa uptime.
Qué falla a medida que esto crece
Más proyectos. Una sesión de tmux por repositorio, nombrada según este; tmux ls es entonces su panel de control. Si no mantiene la disciplina de nombrado, obtendrá sesiones 0, 1, 2. Los puertos se descontrolan de la misma forma: si seis repositorios requieren el puerto :3000, es el momento de dejar de asignarlos manualmente y permitir que una ruta de proxy inverso Traefik para múltiples apps bajo Docker Compose gestione el despacho mediante hostname.
Más personas. Los sockets de tmux son por usuario, por lo que dos desarrolladores en la misma máquina tendrán su propio servidor tmux y no podrán ver las sesiones del otro. Compartir una sesión mediante un socket compartido significa que todos escriben en el mismo shell bajo el mismo usuario Unix, con las consecuencias de auditoría y permisos que esto implica. Usar usuarios separados es la solución correcta y sencilla.
Trabajo no supervisado. tmux es para sesiones interactivas a las que uno se conecta. Los trabajos que se ejecutan programados sin supervisión deben estar en una unidad y un timer de systemd, donde obtienen registro, política de reinicio y persistencia tras el arranque de forma gratuita. Usar tmux para ejecutar un trabajo tipo cron indica que el trabajo debe ser un servicio.
Una última nota: vincule los servidores de desarrollo para que el agente se conecte al 127.0.0.1, no al 0.0.0.0, y acceda a ellos mediante un túnel SSH (ssh -L 3000:127.0.0.1:3000 agent@your-server) en lugar de abrir puertos en ufw. Cuando esté reenviando media docena de puertos, o si un teléfono y un portátil necesitan la misma vista previa, instale una VPN WireGuard autoalojada en la VPS delante de ellos: los servidores de desarrollo se vinculan a una interfaz privada y ufw sigue denegando todo lo que provenga de la interfaz pública. El firewall solo es útil si deja de abrir puertos en él.
Claude Code no es la única opción: ejecutar un agente de IA de programación en una VPS también considera Aider y Goose.
FAQ
¿Claude Code sigue ejecutándose si se pierde la conexión SSH?
Solo si se inició dentro de tmux. Un proceso lanzado directamente desde el shell de SSH es un hijo de ese shell y muere cuando el pty se cierra por la pérdida del enlace. Dentro de tmux, el shell pertenece al servidor tmux desvinculado, por lo que el agente continúa trabajando en la tarea y tmux attach le devuelve al mismo historial de scrollback. Ejecute tmux new -A -s <project> como primer comando tras cada inicio de sesión para evitar este problema.
¿Debo instalar la CLI con sudo npm install -g?
No. Un prefijo global propiedad de root causa errores de EACCES en instalaciones posteriores y archivos propiedad de root en el cache de npm. Configure el prefijo de npm en ~/.npm-global (o use un gestor de versiones como nvm), instale como el usuario sin privilegios agent y exporte ~/.npm-global/bin hacia PATH desde ~/.bashrc, antes de la protección interactiva. Si ya ejecutó sudo npm anteriormente, repare el cache con sudo chown -R $(id -u):$(id -g) ~/.npm.
¿Es seguro el agent forwarding de ssh -A en un servidor que ejecuta un agente?
Otorga mucho más de lo que el trabajo requiere. El forwarding expone el socket de su agente SSH local a cada proceso que se ejecute como ese usuario; por tanto, cualquier proceso en el servidor puede solicitar a su llave que firme para cualquier host alcanzable mientras la sesión esté activa. Genere una llave ed25519 en el servidor y regístrela como una deploy key por repositorio, con acceso de escritura solo si el agente necesita realizar un push.
¿Por qué mi build solo imprime Killed?
Un mensaje de una sola palabra sin stack trace indica el kernel OOM killer. Confírmelo con sudo dmesg -T | grep -i -E 'out of memory|killed process'; desde Node podría ver JavaScript heap out of memory en su lugar. Aplique las soluciones en este orden: añada un swapfile, limite el paralelismo de tests y del compilador, aumente NODE_OPTIONS=--max-old-space-size=... y, finalmente, aumente el tamaño del VPS. Tenga en cuenta que el OOM killer puede seleccionar el servidor tmux en lugar del build, terminando con toda su sesión.
¿tmux o un servicio systemd?
tmux es adecuado para sesiones interactivas a las que se puede conectar, monitorear y escribir, que es exactamente lo que es una sesión de agente. Los trabajos que se ejecutan programados sin supervisión deben ser una unidad y un timer de systemd, donde el logging, la política de reinicio y la persistencia tras el arranque están incluidos. Si intenta usar tmux para ejecutar un trabajo tipo cron, el trabajo debe ser un servicio.