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
andsetsid
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"