Skip to content
shellmap

Run a command as a daemon

Detach a process from the controlling terminal and any parent shell — so it keeps running after logout, SSH disconnect, or terminal close.

How to run a command as a daemon in each shell

Bashunix
nohup ./long-task >/dev/null 2>&1 &

`nohup` ignores SIGHUP (so terminal close doesn't kill it). `&` backgrounds. `>/dev/null 2>&1` discards stdout+stderr (default `nohup` writes to `nohup.out`). Process still in the shell's job table — `disown %1` removes it.

Zshunix
nohup ./long-task >/dev/null 2>&1 &
Fishunix
nohup ./long-task >/dev/null 2>&1 &; disown
PowerShellwindows
Start-Process ./long-task -NoNewWindow -RedirectStandardOutput out.log -RedirectStandardError err.log

`-NoNewWindow` runs in the SAME console (closing the console still kills it). For a truly-detached process: `Start-Process ./long-task -WindowStyle Hidden` (opens new window, hidden — survives parent close).

cmd.exewindows
start /b "" long-task.exe > out.log 2>&1

`/b` runs in the same console without a new window. Empty `""` is the "title" arg (start interprets first quoted arg as window title — without `""`, the path-with-quotes gets mistaken for a title).

Equivalents listed for Bash, Zsh, Fish, PowerShell, cmd.exe.

Gotchas & notes

  • **`nohup` vs `setsid` vs `disown` — three different concerns**: `nohup` ignores SIGHUP (terminal close). `setsid` runs the command in a NEW SESSION — no controlling terminal at all. `disown` removes a job from the SHELL'S JOB TABLE so it survives shell exit. Best practice for "true daemon": `setsid nohup ./cmd </dev/null >/dev/null 2>&1 &; disown` — handles all three concerns. But this is fragile; for anything beyond a one-shot, use a real init system (systemd, launchd, supervisord, pm2). Hand-rolled daemonisation misses things like double-fork (orphan to init), `chdir("/")` (don't hold a working directory open), `umask(0)`, log rotation, restart-on-crash.
  • **`systemd-run --user --scope ./cmd` is the modern Linux answer**: creates a transient systemd unit that's independent of the shell session. `--user` means it goes into the user's session bus (no root needed); `--scope` runs synchronously (like a sub-shell), `--unit name --background` (or just no flag) runs detached. Survives logout if `loginctl enable-linger USER` was set. systemd handles all the daemon hygiene (PID file, logging via journal, restart). Equivalent on macOS: `launchctl submit -l mylabel -- /path/to/cmd` or write a `.plist` LaunchAgent. Equivalent on Windows: NSSM (`nssm install MyService ./cmd.exe`) wraps any binary as a real Windows Service.
  • **Stdin/stdout/stderr redirection matters more than people think**: if you `./cmd &` without redirection, the process inherits the terminal's tty as stdin/stdout/stderr. When the terminal closes, ANY write to that fd produces SIGPIPE → SIGHUP → death (unless `nohup`). Standard pattern: `cmd </dev/null >>cmd.log 2>&1 &`. The `</dev/null` (stdin from null device) prevents the daemon from blocking on a read; `>>cmd.log 2>&1` puts both streams into the log file (appending, so restarts don't clobber). Anti-pattern: `cmd > cmd.log &` — leaves stderr connected to terminal, daemon dies on terminal close.
  • **`pwsh Start-Process` detachment** is more subtle than expected. `Start-Process -NoNewWindow` runs in the SAME console — child dies if parent console closes. `Start-Process -WindowStyle Hidden` opens a new hidden console — survives. For true daemon-like behaviour on pwsh, prefer `Start-Job` (background pwsh runspace) for pwsh code, or wrap external commands in `Start-Process -RedirectStandard*` + register an exit handler. For production "service that survives reboot", install as a Windows Service: `New-Service -Name MyApp -BinaryPathName "C:\path\cmd.exe args"` (built-in, but limited to programs that handle Win32 service control messages natively — most don't). NSSM wraps non-service-aware binaries.
  • **The "session detachment" of `tmux` / `screen` is the practical interactive answer**: instead of daemonising, run inside `tmux new -d -s mywork ./cmd` (creates a detached tmux session running the command). Reattach with `tmux attach -t mywork`; check logs by attaching, scroll back to inspect. Survives SSH disconnect because tmux's daemon (not your shell) owns the child process. Way easier than nohup + log file plumbing for "long-running task I might want to check on". For unattended scheduled work, prefer systemd-run / cron / a real scheduler; for "I'm about to lose this SSH session and don't want to restart the build", tmux/screen is the right tool.

Related commands

Related tasks