Cómo dejar un proceso corriendo en el VPS después de cerrar SSH
En pocas palabras
Todo lo que lanzás en una sesión SSH depende de esa sesión: si se corta la conexión, el proceso recibe la señal de cierre y termina. Para un trabajo de horas que querés poder seguir mirando, usá tmux o screen: el proceso queda dentro de una sesión a la que volvés a conectarte. Para una tarea única que no necesitás mirar, systemd-run la convierte en un servicio temporal con su registro. Y para un programa que tiene que estar siempre, escribí una unidad de systemd: arranca con el servidor, se reinicia si se cae y guarda su salida en journalctl.
Por qué el proceso muere cuando se corta la conexión
Al entrar por SSH, el sistema te asigna una terminal y un intérprete de comandos, y todo lo que lanzás cuelga de ahí. Cuando la conexión se cae, esa terminal desaparece y el sistema les manda a sus procesos la señal SIGHUP, que por defecto los termina. Agregar & al final del comando solo lo manda al fondo: sigue siendo hijo de la misma sesión.
nohup hace que el proceso ignore esa señal, y por eso sobrevive al corte. Pero es una solución a medias: la salida va a parar a un archivo nohup.out, no podés volver a interactuar con el proceso, nadie lo levanta si falla y un reinicio del servidor lo borra del mapa.
Qué herramienta usar en cada caso
Hay cuatro situaciones y cada una tiene su herramienta. La última, las tareas periódicas, tiene guía propia en programar tareas con cron y systemd.
| Lo que necesitás | Herramienta | ¿Sobrevive a un reinicio? |
|---|---|---|
| Un comando largo que querés seguir mirando: una importación, un rsync | tmux o screen | No |
| Una tarea única que no hace falta mirar | systemd-run | No, pero queda registrada |
| Un programa que tiene que estar siempre: una API, un bot, un worker | Unidad de systemd | Sí, arranca solo |
| Algo que corre cada cierto tiempo | Temporizador de systemd o cron | Sí |
tmux: una sesión que te espera
tmux mantiene una terminal viva en el servidor, independiente de tu conexión. Te desenganchás, cerrás la notebook y al día siguiente encontrás todo como lo dejaste, con la salida en pantalla. Está en los repositorios de todas las distribuciones: sudo apt install tmux en Debian y Ubuntu, sudo dnf install tmux en AlmaLinux.
tmux new -s importacion # abre una sesión con nombre
# ... lanzás el comando largo ...
# Ctrl-b y después d: te desenganchás sin cortar nada
tmux ls # lista las sesiones abiertas
tmux attach -t importacion # volvés a entrar
tmux kill-session -t importacion # la cerrás cuando terminó- Para ver lo que ya pasó por pantalla, Ctrl-b y después [ activa el modo de desplazamiento; con q salís.
- Ctrl-b % divide la ventana en dos, útil para tener
htopal lado del proceso. - Si en el servidor ya está screen, sirve igual con otros atajos:
screen -S importacionpara abrir, Ctrl-a d para desengancharte yscreen -r importacionpara volver. Si la conexión se cortó antes de desengancharte y screen dice que la sesión está ocupada,screen -d -r importacionla libera y te la devuelve.
systemd-run: un servicio de un solo uso
Menos conocido y muy práctico: systemd-run lanza un comando como servicio temporal, fuera de tu sesión. Sobrevive al corte de SSH, su salida queda en el journal con fecha y hora, y cuando termina, desaparece solo. Es ideal para una copia grande o una conversión que no necesitás mirar en vivo. Con --uid= elegís con qué usuario corre.
sudo systemd-run --unit=copia-fotos rsync -a /srv/fotos/ /mnt/respaldo/fotos/
systemctl status copia-fotos # mientras corre
journalctl -u copia-fotos -f # la salida, en vivo o cuando ya terminóUna unidad de systemd para lo que tiene que estar siempre
Supongamos un worker en Python que procesa una cola, instalado en /opt/cola con su entorno virtual, que corre con un usuario sin privilegios llamado deploy (si todavía hacés todo como root, antes mirá cómo crear un usuario con sudo). La unidad va en /etc/systemd/system/cola.service, y el nombre del archivo es el nombre del servicio:
[Unit]
Description=Worker de la cola de pedidos
Wants=network-online.target
After=network-online.target mariadb.service
[Service]
User=deploy
WorkingDirectory=/opt/cola
EnvironmentFile=/etc/cola.env
ExecStart=/opt/cola/venv/bin/python worker.py
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.targetAfter=con el nombre de la base hace que arranque después de ella, pero no espera a que acepte conexiones. Si el worker falla por eso,Restart=on-failurelo reintenta cada cinco segundos hasta que la base responde.EnvironmentFile=carga variables como contraseñas o direcciones de APIs desde un archivo aparte. Dale permisos 600 y dejalo con dueño root: systemd lo lee antes de pasar al usuario del servicio.ExecStart=usa la ruta completa del intérprete del entorno virtual. systemd no lee tu .bashrc ni activa entornos, y solo encuentra por nombre los programas de las carpetas estándar como /usr/bin; Node instalado con nvm o cualquier cosa de tu carpeta personal va con la ruta entera.- Sin
User=, el servicio corre como root. ConUser=deploy, la carpeta /opt/cola y todo lo que escriba el programa tienen que pertenecer a ese usuario.
Activarla y probar que vuelve sola
- Avisale a systemd que hay un archivo nuevo o modificado:
sudo systemctl daemon-reload. - Habilitala para el arranque e iniciala en el mismo paso:
sudo systemctl enable --now cola. - Revisá el estado con
systemctl status cola. Tiene que decir active (running) y mostrar las últimas líneas de la salida. - Probá el reinicio automático matando el proceso a propósito con
sudo systemctl kill -s KILL cola. A los cinco segundos, el estado tiene que volver a active. - Reiniciá el servidor con
sudo rebooty, al volver, confirmá consystemctl is-active cola. Es la única prueba de que arranca sola.
Leer la salida en journalctl
Todo lo que el programa escribe en pantalla, errores incluidos, queda en el journal con fecha, hora y nombre del servicio. No hace falta redirigir a un archivo ni armar una rotación.
journalctl -u cola -f # en vivo, mientras probás
journalctl -u cola --since "30 min ago" # la última media hora
journalctl -u cola -n 50 --no-pager # las últimas 50 líneas
journalctl -u cola -b -1 # lo que pasó en el arranque anterior- La última opción necesita que el journal se guarde en disco. Si no existe la carpeta /var/log/journal, el registro vive en memoria y se pierde al reiniciar; se activa con
sudo mkdir -p /var/log/journalysudo systemctl restart systemd-journald. journalctl --disk-usagemuestra cuánto ocupa, ysudo journalctl --vacuum-time=30dborra lo que tenga más de un mes.
Cuando no arranca: qué dice el estado
Si systemctl status muestra failed, la línea del proceso principal trae un código que casi siempre alcanza para saber qué pasó:
| Código en el estado | Qué significa | Qué revisar |
|---|---|---|
| status=203/EXEC | No pudo ejecutar el comando | La ruta de ExecStart, que el archivo exista y tenga permiso de ejecución |
| status=217/USER | El usuario de User= no existe | El nombre, con id deploy |
| status=200/CHDIR | No pudo entrar a WorkingDirectory | Que la carpeta exista y el usuario tenga permiso |
| status=1/FAILURE | El programa arrancó y terminó con error | La salida en journalctl: el error es del programa, no de systemd |
| start-limit-hit | Se reinició demasiadas veces seguidas | La causa del error; después, sudo systemctl reset-failed cola |
systemd deja de reintentar si una unidad arranca más de 5 veces en 10 segundos. Con RestartSec=5 no se llega a ese límite; sin esa línea, los reintentos son casi inmediatos y se llega enseguida.
Dos trampas que no dejan un código claro
La primera: tuberías, redirecciones y variables del intérprete (|, >, $HOME) no funcionan en ExecStart, porque systemd ejecuta el programa directamente, sin un intérprete de comandos en el medio. Si los necesitás, el comando entero va dentro de /bin/sh -c "...".
La segunda: un programa que se manda solo al fondo. systemd ve terminar el proceso que lanzó y da el servicio por muerto, o lo reinicia en un ciclo. Casi todos los programas tienen una opción para quedarse en primer plano, y es la que hay que usar.
Preguntas frecuentes
¿Cómo paro el servicio sin que vuelva en el próximo reinicio?
sudo systemctl disable --now cola lo detiene y lo saca del arranque en un solo paso. Si solo querés pararlo un rato, sudo systemctl stop cola: queda quieto hasta que lo arranques o reinicies el servidor.
¿Puedo limitar cuánta memoria usa el servicio?
Sí. Con sudo systemctl edit cola agregá, bajo el encabezado [Service], la línea MemoryMax=512M, y reiniciá el servicio. El cambio queda en un archivo aparte, sin tocar el original. Si el programa pasa el límite, el sistema lo corta y Restart= lo vuelve a levantar. systemctl status muestra el consumo actual.
¿Si uso Docker necesito todo esto?
Para los contenedores, no: Docker los levanta solo si los creaste con --restart unless-stopped y el servicio docker está habilitado. Lo que no conviene es mezclar una política de reinicio de Docker con una unidad de systemd que arranca el mismo contenedor, porque terminan peleándose.
¿Puedo dejar una aplicación en producción dentro de tmux?
Funciona hasta el primer reinicio del servidor o la primera caída del programa, y ahí nadie la levanta. Para producción, unidad de systemd; tmux es para el trabajo que estás haciendo vos en ese momento.
Seguí leyendo
Primeros pasos con tu VPS: la primera hora, en orden
Qué hacer apenas recibís un VPS con Linux, antes de instalar tu aplicación: actualizar, crear tu usuario, entrar con clave SSH, firewall, hora y respaldos.
5 min de lectura
Programar tareas en Linux con cron y con timers de systemd
Cómo se escribe una línea de crontab, horarios útiles, por qué una tarea anda a mano y falla en cron, y cuándo conviene un timer de systemd.
8 min de lectura
Usuario administrador con sudo: crearlo y dejar de entrar como root
Cómo crear tu usuario con sudo en Debian, Ubuntu y AlmaLinux, pasarle la clave SSH, probarlo y recién después cerrar el acceso de root por SSH.
7 min de lectura
