Start and Stop a Daemon Safely with start-stop-daemon
start-stop-daemon starts and stops one daemon precisely, so an init script never kills the wrong process by accident. That precision comes from matching options you have to get right yourself. The examples below follow the installed dpkg start-stop-daemon 1.22.6 on this machine.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about fifteen minutes. You need a shell and a daemon with a documented executable path, service user and, ideally, a pidfile. Starting or stopping a real service normally needs elevated privileges; the read-only checks and dry runs below do not.
1. Confirm the installed command
Check the binary and package version before you trust any example, this one included:
$ command -v start-stop-daemon
/usr/sbin/start-stop-daemon
$ start-stop-daemon --version
start-stop-daemon 1.22.6 for Debian
$ dpkg-query -W -f='${Package} ${Version}\n' dpkg
dpkg 1.22.6
The command has four main actions: --start, --stop, --status and --help. Matching options decide which existing process counts as "the daemon". Starting adds --exec or --startas, and anything after -- is passed straight to the program.
Checkpoint
Write down the real executable, service account and pidfile path for your daemon. Do not substitute a process name from memory.
2. Build a precise match
For a real service, combine more than one match restriction. A typical match uses the executable, user and pidfile together:
$ sudo start-stop-daemon --start --test --oknodo \
--exec /usr/sbin/exampled \
--user example \
--pidfile /run/exampled.pid \
--startas /usr/sbin/exampled -- --foreground
--test prints the action it would take and changes nothing, which makes it the safest way to catch a misspelled path or a match that is broader than you intended. The -- before --foreground separates start-stop-daemon's own options from the daemon's arguments.
- Do not use only
--userunless you genuinely mean to act on every matching process that account owns. - Do not use only a pidfile unless you have checked its ownership and contents; a stale pidfile can point at an unrelated process. On dpkg 1.22.6, a world-writable pidfile, or an unprivileged daemon writing one that a privileged command later trusts, is rejected outright as a security risk.
- Prefer an absolute
--execpath. Interpreted scripts can match their interpreter instead of the script, and a process inside a chroot can match the same executable too; add another restriction when that matters.
3. Start only after the dry run is right
Repeat the command without --test once its proposed action looks correct. This actually changes service state, so use whatever account and maintenance controls your host requires:
$ sudo start-stop-daemon --start --oknodo \
--exec /usr/sbin/exampled \
--user example \
--pidfile /run/exampled.pid \
--startas /usr/sbin/exampled -- --foreground
--start checks for a matching process first, and if one already exists, no second instance gets started. Without --oknodo that no-op returns status 1; with it, an already-running daemon counts as success, which an init script usually finds easier to handle.
Verify the result using the same match:
$ sudo start-stop-daemon --status \
--exec /usr/sbin/exampled \
--user example \
--pidfile /run/exampled.pid
$ printf 'status: %s\n' "$?"
status: 0
For --status, status 0 means the program is running, status 1 means it is not but the pidfile exists, status 3 means it is not running at all, and status 4 means the status could not be determined. Treat a stale pidfile as something to investigate, never as permission to kill a process by number.
4. Stop it with a bounded wait
Stopping sends SIGTERM by default. Start with a dry run, then repeat with the same exact match:
$ sudo start-stop-daemon --stop --test --oknodo \
--exec /usr/sbin/exampled \
--user example \
--pidfile /run/exampled.pid \
--retry 5
The timeout form --retry 5 means TERM, wait up to five seconds, then KILL, then wait another five. KILL cannot be caught by the daemon, so it can lose in-memory work; use it only after giving the normal shutdown path a fair interval.
Once the dry run looks right, stop the service for real:
$ sudo start-stop-daemon --stop --oknodo \
--exec /usr/sbin/exampled \
--user example \
--pidfile /run/exampled.pid \
--retry 5
$ sudo start-stop-daemon --status \
--exec /usr/sbin/exampled \
--user example \
--pidfile /run/exampled.pid
$ printf 'status: %s\n' "$?"
status: 3
If the daemon leaves its pidfile behind, add --remove-pidfile to the stop command, but only once you have checked the file really is the one you intend to remove. It deletes the file after termination. Recovery without it is simple: remove the stale pidfile by hand once you have confirmed no matching process remains, or let the daemon's own service wrapper handle the cleanup.
5. Choose a custom retry schedule only when needed
Some daemons need more time or a different escalation. A schedule names signals and waits explicitly:
$ sudo start-stop-daemon --stop --oknodo \
--exec /usr/sbin/exampled \
--user example \
--pidfile /run/exampled.pid \
--retry=TERM/30/KILL/5
Each item is separated by a slash, so this schedule sends TERM, waits 30 seconds, sends KILL, then waits another five. If the schedule runs out while a process is still alive, the command returns status 2. A retry schedule overrides --signal, so do not add both expecting them to combine.
6. Read exit statuses in scripts
For start and stop, status 0 means the requested action happened, or that --oknodo waved through an intentional no-op. Status 1 means nothing happened because the match was already in the requested state, status 2 is an unfinished retry schedule, and status 3 is another kind of error. Keep these cases visible in a wrapper instead of collapsing every non-zero result into "service failed":
if start-stop-daemon --start --oknodo \
--exec /usr/sbin/exampled --user example \
--pidfile /run/exampled.pid --startas /usr/sbin/exampled; then
printf '%s\n' 'exampled is starting or already running'
else
rc=$?
printf 'start-stop-daemon failed: %s\n' "$rc" >&2
exit "$rc"
fi
For long-lived children, always use a pidfile. Without one, stop scans matching processes much like killall, and a daemon's own children can get swept up too. A pidfile narrows the operation, but only when its lifecycle and permissions are actually trustworthy.
Done means
- You confirmed dpkg 1.22.6 and the executable path.
- Your match includes enough restrictions to identify one daemon, not a family of processes.
- You ran
--testbefore starting or stopping anything for real. - You understand that
--oknodochanges how a no-op is reported. - You used a bounded retry and treated KILL as disruptive, not routine.
- You checked the final state with
--statusand read its code correctly.