Skip to content

After SSH Disconnects, Is Your Program Still Running? ​

Many people have been in this situation: you start a script on a server, close the SSH window, and immediately wonder whether the process has disappeared with it.

The answer is not as simple as “the server is still on, so everything is fine.” What matters is whether the program still depends on the terminal and shell session that just closed, and whether a more appropriate tool is managing it. Three common candidates are nohup, tmux, and systemd, but they solve fundamentally different problems. nohup deals with hangup signals and output destinations. tmux gives you a terminal session you can reconnect to. systemd manages the complete lifecycle of a service. Treating them as interchangeable “keep-alive spells” can produce an absurd result after SSH disconnects: the process is technically alive, but the task is out of control, its logs are missing, and nobody knows its exit status.

Here is a rough selection guide before we examine the details:

ScenarioBetter fit
Run a one-off command without reconnecting to its interactive interfacenohup, or a proper job queue
Disconnect and later return to the same terminaltmux
Run a long-lived service with startup, restart, and logging requirementssystemd
Run a long build, migration, or batch operationUsually tmux; use a job manager when it is fully unattended

One SSH login contains several separate layers ​

SSH connections, terminals, shells, and processes are often discussed as if they were the same thing, but they are separate layers:

text
Local SSH client
        │
        │ encrypted connection
        ▼
sshd ── login shell ── terminal/PTY ── your program

The OpenSSH ssh command connects to the remote sshd. If it requests a pseudo-terminal, the server allocates a PTY and runs a shell or a specified command inside it, forwarding standard input and output over the encrypted channel.[1] Each command you enter is normally started by that login shell, which connects the child process to the terminal's standard streams.

That means several conditions must be considered separately: whether the SSH connection is open, whether the PTY is open, whether the login shell has exited, whether a particular process is still running, and—if a service manager owns it—what state that service is in. An SSH disconnect does not mean the kernel instantly kills every child process. It also does not guarantee that every program will continue normally. The result depends on signal handling, terminal dependencies, and how the parent process and login session are cleaned up.

If what has to survive the disconnect is not a program but a tunnel, that is a separate mechanism with its own rules: local, remote, and dynamic forwarding each behave differently when the underlying connection drops, as described in port forwarding.

Why do some programs exit after a disconnect? ​

When an interactive shell exits, processes associated with it may receive SIGHUP, the hangup signal. A program may terminate under its default behavior, catch the signal and handle it, or ignore it because it does not depend on that terminal.[5]

Even if SIGHUP does not terminate the program immediately, closing the terminal can expose other problems: the program may keep writing to a terminal that no longer exists; it may block while waiting for standard input; shell job-control relationships may end; logs that existed only on the terminal may become inaccessible; or the task may finish without leaving a clear exit status.

So “Is the process still running after SSH disconnects?” is not the most useful question. A better one is: does this task have input, output, lifecycle management, and result recording that are independent of this SSH connection?

nohup: make a command ignore hangup signals ​

The name nohup means “no hangup.” It makes a command ignore the hangup signal. If standard output still points to a terminal, GNU nohup appends it to nohup.out and handles standard error similarly.[2]

bash
nohup ./backup.sh > backup.log 2>&1 &
pid=$!
printf 'pid=%s\n' "$pid"

This line combines three separate actions: nohup makes the script ignore the hangup signal, > backup.log 2>&1 redirects standard output and error to a log file, and the final & lets the shell return without waiting for the script to finish. After reconnecting, you can inspect it with:

bash
ps -p "$pid" -o pid=,stat=,etime=,cmd=
tail -n 50 backup.log

But nohup is not a complete service manager. It does not tell you whether the task succeeded, restart it after a crash, or preserve an interactive terminal you can rejoin. A surviving PID does not prove the task is healthy: it may be stuck, repeatedly logging errors, or may have already finished without recording its exit status. If the task needs interaction or live input, or if you want to return to the same terminal, nohup is usually not the best fit.

tmux: separate the terminal session from the SSH connection ​

tmux is a terminal multiplexer. It creates an independent session on the server and runs a shell inside it. Your SSH client is merely one terminal attached to that session. If SSH disconnects, the tmux session remains on the server and can be attached again later.[3]

bash
tmux new -s deploy
cd /srv/example
./deploy.sh

To leave without ending the session, press Ctrl-b, then d. After reconnecting through SSH:

bash
tmux ls
tmux attach -t deploy

tmux is a good fit when you need to watch live build or deployment output, respond during a data migration, run a temporary development server, or keep working despite an unstable network. Its key advantage is that the shell state, working directory, and foreground process remain in place. But tmux solves session persistence, not program reliability. The program can still crash, exit because of application logic, or fail for another reason. After reconnecting, you still need to inspect its output and status:

bash
tmux ls
ps -ef --forest

systemd: manage the lifecycle of a long-running service ​

Web servers, background workers, message consumers, and other long-running programs are better managed by systemd. A service unit describes how a program starts and stops, whether it should restart after failure, and the conditions under which it should run.[4]

ini
[Unit]
Description=Example worker
After=network-online.target

[Service]
Type=simple
WorkingDirectory=/srv/example
ExecStart=/srv/example/bin/worker
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

Save it as /etc/systemd/system/example-worker.service, then run:

bash
sudo systemctl daemon-reload
sudo systemctl enable --now example-worker.service
systemctl status example-worker.service
journalctl -u example-worker.service -f

systemd is appropriate for long-running services because it makes previously informal knowledge explicit: who starts the service, its working directory and command, whether it restarts after failure, where its logs live, whether it starts at boot, its current state, and why it failed last time. This is completely different from the problem tmux solves. A production web service normally should not live in one administrator's tmux window, and nohup should not be expected to replace restart policies, dependency management, or permission configuration.

Comparing the three tools ​

Capabilitynohuptmuxsystemd
Continue after SSH disconnectsUsuallyYesYes
Reattach to the original interactive terminalNoYesNot applicable
Output locationRedirected filePreserved in the sessionJournal
Automatic restart after failureNoNoConfigurable
Start at bootNoNoConfigurable
How to inspect statusCheck PID and logs yourselfInspect sessions and processessystemctl status
Suitable by itself for long-running servicesNot recommendedNot recommendedYes
Suitable for temporary interactive tasksSometimesYesUsually not

Continuing after an SSH disconnect and being suitable for production are two different things. Detaching a command from one connection does not make its lifecycle observable, recoverable, or auditable.

What should you inspect after reconnecting? ​

Do not stop after running ps and seeing that a process exists. Check according to the type of task:

bash
# Temporary task
ps -ef | grep '[j]ob.sh'
tail -n 100 job.log

# tmux task
tmux ls
tmux attach -t maintenance

# systemd service
systemctl is-active example-worker.service
systemctl status example-worker.service
journalctl -u example-worker.service --since '30 minutes ago'

Also verify whether the process is still making progress, whether CPU, memory, or disk usage looks abnormal, whether logs continue to grow, whether the task has completed, whether it produced a verifiable result, and whether it restarted or ran more than once. “The process is alive” is one item on the checklist, not proof of success.

Common misunderstandings ​

Does adding & prevent a command from exiting when SSH disconnects? Not necessarily. & only lets the shell continue without waiting for the foreground command. It does not handle hangup signals, standard streams, or service restarts.

Can nohup restore the original session? No. It addresses the command's dependency on the hangup signal and may redirect terminal output, but it does not preserve an interactive terminal that you can reattach to.

Will programs inside tmux run forever? Of course not. They can still crash, exit intentionally, or be killed by the OOM killer. tmux preserves a reconnectable session; it does not guarantee program correctness.

Is systemd only for starting programs at boot? No. It also manages service state, stopping behavior, restart policies, dependencies, and logging. Boot startup is only one option.

Does an existing PID prove the task succeeded? No. You must consider exit status, logs, output files, service state, and the actual business result.

An SSH connection is only a channel for accessing a server; it does not manage a task's lifecycle. The questions that actually decide the tool are: does this task need interaction, do you need to reattach to it, should it restart automatically, does it need to start at boot with centralized logging? Answering those is more useful than hunting for one universal "keep-alive command." A client such as Termark is not a replacement for tmux, systemd, or a job queue — it gets you to the server so that those server-side tools can own the lifecycle: connect to the host, create or reattach a tmux session, read logs, processes, and service state, pull a log file back over SFTP with SFTP directory following enabled, and reconnect later to check the result. Keeping that boundary clear tells you whether to investigate the connection, the session, or the service configuration when something goes wrong.

References ​

[1] OpenSSH ssh manual

[2] GNU Coreutils nohup

[3] tmux manual

[4] systemd.service manual

[5] GNU Bash Signals

Termark · SSH client and terminal workspace