Long-Running Commands Over SSH: Keep Jobs Running After Disconnect

Deepak Prasad
Tested on RHEL 10.2 (Coughlan) — vm2.lab.example as SSH client; vm1.lab.example as remote host
Package openssh-clients 9.9p1-25.el10_2 and openssh-server 9.9p1-25.el10_2
Applies to RHEL, Rocky Linux, AlmaLinux, Fedora, Ubuntu, Debian, and other Linux systems with OpenSSH
Privilege Normal user on the SSH client; the remote account must be allowed to run the command (often your own login user, sometimes sudo for system paths)
Scope Detaching long-running remote commands from an SSH session with nohup, setsid, tmux, GNU screen, and interactive disown; explicit log redirection; verifying that a job survives after SSH exits. Does not cover SSH idle keepalive tuning or closing a hung interactive session.
Related guides ssh command
tmux cheat sheet
screen command
SSH multiplexing
Idle SSH timeouts

When you start a database backup or a long compile over SSH, closing the laptop lid or losing Wi‑Fi should not decide whether the job finishes. Backgrounding with & and truly detaching from the session are not the same thing: a process must stop depending on the SSH session's terminal and I/O, or use a mechanism such as nohup, setsid, or a terminal multiplexer built to survive disconnects.

The examples below use the lab-server SSH alias on vm2.lab.example (pointing at vm1.lab.example). Replace that name with your own host alias or user@remote.


Why & alone is not enough over SSH

A common mistake is wrapping a background job in a one-line ssh command and assuming the ampersand fully detaches it:

bash
time ssh lab-server "bash -c 'sleep 8 &'"

Sample output:

output
real    0m8.424s
user    0m0.015s
sys     0m0.015s

The real time matches the sleep length, so your SSH client stayed connected for the whole job. That is the opposite of “walk away.”

& backgrounds the command in the remote shell, but it does not fully detach the process from the SSH connection. The child can inherit stdin, stdout, and stderr connected to the SSH channel. Even after the remote shell exits, those descriptors may stay open, so the local ssh process can keep waiting for EOF. Hangup behavior also depends on the shell, whether a pseudo-terminal is allocated, and session setup — not on & alone.

Use one of the methods below instead of a bare & inside ssh '…'.

Method Best for
nohup Ignore hangup signals for a command and explicitly redirect its I/O
setsid Start the process in a separate session, detached from the SSH terminal/session relationship
tmux / screen Interactive work, live output, and reattaching later
disown A job already running in your interactive Bash shell

Run a detached command with nohup

nohup (no hang up) arranges for the command to ignore SIGHUP. Over SSH you still need explicit I/O redirection — do not rely on automatic nohup.out behavior:

bash
ssh lab-server 'nohup long-command > ~/job.log 2>&1 < /dev/null & echo $! > ~/job.pid'

GNU nohup redirects stdout to nohup.out only when stdout is attached to a terminal. In a typical ssh host '…' one-liner, stdout is a pipe, not a tty, so nohup may leave that stream connected to the SSH channel instead of creating nohup.out. Redirecting stdin, stdout, and stderr avoids that ambiguity.

Replace long-command with your backup script, build, or installer. The remote shell should return immediately while the job keeps running.

Confirm the process survived after SSH exits using the PID file:

bash
ssh lab-server 'ps -p "$(cat ~/job.pid)" -o pid=,cmd='

Sample output (while the job is still running):

output
399417 sleep 15

That ps line checks the exact PID you recorded, which is more reliable than grepping the process list. Tail the log while you wait:

bash
ssh lab-server 'tail -f ~/job.log'

Press Ctrl+c on the client to stop following the log; that does not stop the remote job.


Run a command in a new session with setsid

setsid runs the command in a separate session, which helps detach it from the SSH terminal/session relationship. In practice it is often paired with the same redirection pattern as nohup:

bash
ssh lab-server 'setsid long-command > ~/job.log 2>&1 < /dev/null &'

Prefer setsid when you want a clean session boundary. Prefer nohup when you explicitly want hangup immunity for a single command. Either way, keep > log 2>&1 < /dev/null on non-interactive SSH jobs.


Keep an interactive job with tmux

Terminal multiplexers fit longer work where you want to reconnect and see live output. Install tmux on the remote host once if it is not already present (dnf install tmux on RHEL-family systems, apt install tmux on Debian or Ubuntu).

Start an interactive session:

bash
ssh -t lab-server 'tmux new-session -s deploy'

Inside the tmux pane, run your long command normally:

bash
long-running-command

Output stays visible in the pane while the command runs. Detach without stopping the session: press Ctrl+b, then d.

Your SSH connection can close; the tmux server on the remote host keeps the session alive. Reconnect later:

bash
ssh -t lab-server 'tmux attach-session -t deploy'

List sessions when you forget the name:

bash
ssh lab-server 'tmux ls'

For day-to-day key bindings and pane splits, see the tmux cheat sheet.


Use disown for a job already running

disown is a Bash builtin for jobs you started in an interactive SSH shell you already opened:

bash
ssh -t lab-server

On the remote shell, start the job in the background:

bash
long-running-command > ~/job.log 2>&1 &

List background jobs so you know which number Bash assigned:

bash
jobs

Sample output:

output
[1]+  Running                 long-running-command > ~/job.log 2>&1 &

Mark the job so Bash does not send it SIGHUP when the shell exits (replace %1 with the job number shown by jobs):

bash
disown -h %1

disown -h marks the job so Bash does not send that job SIGHUP when Bash would otherwise do so. It does not change the process's global SIGHUP disposition. disown %1 (without -h) removes the job from Bash's job table entirely.

Log out with exit. The job should keep running because you disowned it before the interactive shell closed.

Do not rely on disown inside a non-interactive ssh host '…' one-liner. In testing, bash -c 'sleep 8 & disown' still blocked the SSH client for eight seconds — the same wait as a plain background job. For automation, use nohup or setsid with explicit redirection instead.


GNU Screen alternative

GNU Screen solves the same problem as tmux: a persistent terminal you detach and reattach. Install the screen package on the remote host when you prefer Screen's interface or legacy scripts.

Start an interactive session:

bash
ssh -t lab-server 'screen -S backup'

Run your command inside Screen, detach with Ctrl+a then d, and reattach later:

bash
ssh -t lab-server 'screen -r backup'

Most new deployments standardize on tmux, but Screen remains common on older servers and jump hosts.


Check the job and its output

After you detach, verify once that the work is still running and that output is going where you expect:

bash
ssh lab-server 'ps -p "$(cat ~/job.pid)" -o pid=,cmd='

Read the log:

bash
ssh lab-server 'tail -20 ~/job.log'

For tmux or Screen, list or attach to the session instead of hunting a PID:

bash
ssh lab-server 'tmux ls'

If the log stays empty but the process is gone, the command failed early — read the last lines of the log and rerun with a shorter test command before you launch a multi-hour job.


Troubleshooting

Symptom Likely cause Fix
SSH client waits as long as the remote command Background job still connected to the SSH channel's stdio Use nohup or setsid with > log 2>&1 < /dev/null &, or run inside tmux/screen
Job stops when you close the terminal Process still tied to the session; hangup sent or channel closed Restart with explicit redirection and nohup/setsid, or use tmux/screen
No nohup.out after ssh host 'nohup cmd &' stdout was a pipe, not a terminal Redirect explicitly: > ~/job.log 2>&1 < /dev/null
disown did not free the SSH client Non-interactive ssh '…' one-liner Use nohup/setsid, or ssh -t and disown in an interactive shell
Garbled or missing live output in tmux Output redirected away from the pane, or session already exited Run the command inside an attached tmux pane; do not redirect all output away if you need to watch it
Connection drops before you detach Network idle timeout unrelated to detachment Detach the job first; tune ServerAliveInterval separately if idle timeouts kill sessions you still need open

References


Summary

Long-running work over SSH fails quietly when the remote command stays bound to the session's I/O. A plain ssh host 'cmd &' one-liner often blocks the client until the job finishes because the background process can inherit the SSH channel's file descriptors — not because & alone is a complete detachment mechanism.

For scripts and automation, use explicit redirection with nohup or setsid:

bash
ssh host 'nohup command > ~/job.log 2>&1 < /dev/null &'

Do not assume nohup.out appears on a non-interactive SSH job; GNU nohup only performs that automatic redirection when the stream is terminal-connected.

For interactive work — watching a build, adjusting flags, running follow-up commands — start tmux or GNU Screen with ssh -t, run the command inside the session, detach with the multiplexer key sequence, and attach later. disown -h is the rescue move in an interactive Bash shell: it stops Bash from sending SIGHUP to that job when you exit, but it is a poor fit for non-interactive one-liners.

After you detach, check once with ps -p "$(cat ~/job.pid)" or tmux ls and read the log path you chose. Start long work with one of these patterns before you close the laptop; use SSH keepalive settings separately when idle timeouts drop sessions you still need open.


Frequently Asked Questions

1. Will my command keep running if I close the SSH window?

Only if you detach it from the SSH session first. Backgrounding with & does not fully detach the process from the SSH channel. Use nohup or setsid with explicit redirection, or start the work inside tmux or GNU Screen, before you disconnect.

2. What is the difference between nohup and tmux?

nohup runs one non-interactive command with hangup protection and log redirection. tmux keeps a persistent terminal session you can detach from and reattach later to watch progress or run more commands.

3. Does disown work in an ssh one-liner?

Unreliably. Over a non-interactive ssh command, Bash may still wait for a background job even after disown, because the job can remain connected to the SSH channel. Use disown in an interactive shell, or prefer nohup or setsid for one-shot automation.

4. Where does nohup write output if I do not redirect it?

GNU nohup redirects stdout to nohup.out only when stdout is attached to a terminal. Over a typical ssh host command one-liner, stdout is a pipe, so nohup may leave that descriptor connected instead of creating nohup.out. Always redirect stdin, stdout, and stderr explicitly for remote jobs.

5. Do I need tmux if I already use nohup?

Not for every job. Choose nohup or setsid when you only need fire-and-forget execution with a log file. Choose tmux or GNU Screen when you want to reconnect, watch live output, or run several related commands in one session.
Omer Cakmak

Linux Administrator

Highly skilled at managing Debian, Ubuntu, CentOS, Oracle Linux, and Red Hat servers. Proficient in bash scripting, Ansible, and AWX central server management, he handles server operations on OpenStack, KVM, Proxmox, and VMware.

  • Debian
  • Ubuntu
  • Linux
  • Red Hat Enterprise Linux
  • Shell Script
  • System Administration