How to Keep SSH Session from Hanging When Launching Background Processes Remotely


1 views

When automating deployments across multiple servers, many developers encounter the frustrating situation where SSH commands hang indefinitely when trying to launch background processes. The core issue manifests when running commands like:

ssh user@example.com "/path/to/service &"

Despite the ampersand indicating background execution, the SSH session refuses to terminate, causing deployment scripts to stall.

This behavior occurs because SSH maintains the connection when any child process (even backgrounded ones) still holds references to the session's standard I/O streams. There are several technical approaches to properly detach the process:

Option 1: Using nohup with output redirection

ssh user@example.com "nohup /path/to/service > /dev/null 2>&1 &"

Option 2: Utilizing the disown command

ssh user@example.com "/path/to/service & disown"

Option 3: Full process detachment with setsid

ssh user@example.com "setsid /path/to/service >/dev/null 2>&1 </dev/null &"

For robust deployments, consider this bash function that handles both process launch and status verification:

function remote_start() {
    local host=$1
    local user=$2
    local cmd=$3
    local log=$4
    
    ssh ${user}@${host} bash <<EOF
    nohup ${cmd} > ${log} 2>&1 &
    disown
EOF

    # Verify process started
    ssh ${user}@${host} "pgrep -f '${cmd}'" || {
        echo "Process failed to start" >&2
        return 1
    }
}

When dealing with systemd-managed services, the cleanest approach is to use native service commands:

ssh user@example.com "systemctl --user start servicename"

For containerized environments, Docker provides proper process detachment:

ssh user@example.com "docker run -d --name service image command"

When executing remote processes via SSH, many developers encounter a frustrating issue where the SSH session hangs and never returns control to the local terminal. This commonly occurs when trying to start long-running processes or services.

Basic commands like ps or who complete quickly because they:

  • Execute and terminate immediately
  • Don't maintain open file descriptors
  • Don't interact with the terminal

Processes that daemonize or run continuously behave differently by:

  • Keeping stdout/stderr open
  • Maintaining association with the terminal
  • Not properly detaching from the session

Here are all working approaches with examples:

1. Using nohup + disown

ssh user@host "nohup /path/to/command > /dev/null 2>&1 & disown"

2. Redirecting All Output

ssh user@host "/path/to/command >/dev/null 2>&1 &"

3. Using setsid

ssh user@host "setsid /path/to/command </dev/null >/dev/null 2>&1 &"

4. Full Detach with tmux/screen

ssh user@host "tmux new-session -d -s myprocess '/path/to/command'"

For production scripts, combine several techniques:

ssh -f user@host "sh -c 'nohup setsid /path/to/command </dev/null >/var/log/command.log 2>&1 &'"

This approach:

  • Uses -f to put SSH in background
  • Combines nohup and setsid for complete detachment
  • Properly handles all file descriptors
  • Logs output to a file

If processes still hang, check:

# Verify process status
ssh user@host "ps aux | grep command"

# Check open files
ssh user@host "lsof -p [PID]"

# Verify no stdout/stderr leakage
ssh user@host "ls -la /proc/[PID]/fd"