A service stuck in "activating" forever, despite the process clearly running, usually never called systemd-notify to say it was ready. This guide gives you the command patterns for that: report readiness, publish a status line, and announce reload or shutdown. Allow about 15 minutes to adapt them to an existing service.
The examples match the installed systemd-notify from systemd 255.4-1ubuntu8.17. You need a shell, a service unit you can safely inspect, and elevated privileges only when installing a unit under /etc/systemd/system/ or asking systemd to reload it. This guide sends notifications only from a service context: run the same command in an ordinary terminal and it notifies nobody. On this machine that looks like No status data could be sent: $NOTIFY_SOCKET was not set, with an exit status of 1.
Start by confirming the version and whether this boot was actually managed by systemd:
$ systemd-notify --version
systemd 255 (255.4-1ubuntu8.17)
$ systemd-notify --booted
$ printf 'boot check: %s\n' "$?"
boot check: 0
--booted is a check, not a notification: it sends no message and returns zero when systemd booted the machine. Do not treat a successful boot check as proof your particular service accepts notifications, that is a separate question entirely.
Checkpoint: if --booted is non-zero, stop here. A service unit may still exist, but this guide's notification path is not available through the expected systemd service manager.
The service needs a notification type, and systemd needs to accept messages from the process sending them. A minimal unit fragment looks like this:
[Service]
Type=notify
NotifyAccess=main
ExecStart=/usr/local/libexec/example-daemon
Type=notify makes readiness depend on a READY=1 message, and NotifyAccess=main accepts messages from the service's main process, a sensible narrow default to start with. If a helper process needs to send the message instead, the unit needs a deliberately chosen access setting such as exec or all, with the security trade-off understood first. The manpage is blunt about this: systemd refuses messages when NotifyAccess= is not appropriate.
Do not edit a production unit blindly. Before changing one, save its current contents and identify a rollback path, since unit edits affect service start-up and can stop the service starting at all.
After installing or changing a unit, an administrator can ask systemd to reread unit files:
$ sudo systemctl daemon-reload
$ systemctl cat example.service
The first command changes systemd's in-memory configuration but does not restart the service. The second confirms the intended Type=notify, NotifyAccess and ExecStart are actually visible. To undo a test unit change, restore the previous file and run sudo systemctl daemon-reload again. Do not reach for restart just to test the file if interrupting the service is unsafe.
Call systemd-notify --ready only once the daemon has finished the work that makes it usable, such as opening its listening socket or loading its configuration:
#!/bin/sh
set -eu
# Replace this with the daemon's actual preparation step.
/usr/local/libexec/example-daemon --prepare
systemd-notify --ready --status="Accepting requests"
# Continue with the daemon's normal foreground loop.
exec /usr/local/libexec/example-daemon --foreground
--ready is shorthand for READY=1. The status text is optional, human-readable information shown by systemctl status. Keep the service in the foreground unless the daemon's own design demands otherwise, so systemd can keep tracking the process it started.
For an existing daemon, put the call in the wrapper or program that systemd starts, not in a one-off interactive shell. A notification from a terminal has no service socket waiting to receive it.
Checkpoint: inspect readiness and the status line after starting the service in a safe maintenance window:
$ systemctl status --no-pager example.service
$ systemctl is-active example.service
active
The exact status display depends on the unit and daemon. If a Type=notify service stays stuck in "activating", check first that the notification line was actually reached, then read its journal:
$ journalctl -u example.service -b --no-pager
Use a status update for progress that helps an operator but does not mean the service is ready:
systemd-notify --status="Loading tenant configuration"
# load configuration here
systemd-notify --status="Opening listener"
# open the listener here
systemd-notify --ready --status="Accepting requests"
A status string does not replace --ready for a Type=notify unit, and it is not a health check either: a successful notification only says systemd accepted the message. The daemon still needs its own checks for dependencies, configuration validity and request handling.
When a notification unexpectedly fails, capture the exit status immediately:
if ! systemd-notify --status="Accepting requests"; then
status=$?
printf 'notification failed with status %s\n' "$status" >&2
exit "$status"
fi
In a real script, prefer the simpler form below when you need the original status reliably. The negated command above makes $? describe the conditional's result rather than the notification itself:
systemd-notify --status="Accepting requests"
status=$?
if [ "$status" -ne 0 ]; then
printf 'notification failed with status %s\n' "$status" >&2
exit "$status"
fi
Do not hide a failed notification with || true while diagnosing a stuck service. Check the unit's notification access, the process sending the message, and the journal before you touch privileges.
For a service using notification-aware reload semantics, --reloading marks the start of a reload cycle. Send --ready again once the new configuration is active:
systemd-notify --reloading
# validate and apply the new configuration
systemd-notify --ready --status="Configuration reloaded"
--reloading and --stopping were added in systemd 253, so both are present in the installed systemd 255 package. --stopping announces the start of shutdown:
systemd-notify --stopping --status="Draining requests"
# finish graceful shutdown work here
These messages do not perform the reload or stop the process themselves. They describe work your daemon is already doing. Keep the actual signal handling and configuration transaction in the daemon, and make sure a failed reload leaves the old working configuration intact wherever the application supports that.
First, do not add --no-block to make a wrapper look faster unless it was spawned directly by systemd and you understand the sender identity. Normally systemd-notify waits for the manager to process the notification. With --no-block, an auxiliary process can exit before systemd even attributes the message, creating a race. The option was added in systemd 246.
Second, be careful with --pid=. The default tries to send as the invoking process and falls back to the systemd-notify process itself. --pid=parent explicitly reports the calling process's PID, while --pid=self reports the notification command's own PID. If a new process claims to be the service main PID without having been forked by systemd, the manpage requires NotifyAccess=all or the message may simply be ignored. Do not widen that setting without reviewing who else can send messages to the unit.
--exec can send a notification and then replace itself with another command, but the separator has to be escaped from the shell:
$ systemd-notify --ready --status="Ready" \; /usr/local/libexec/example-daemon --foreground
Use this only when preserving the process identity is part of the design; it was added in systemd 254. Test it with a harmless command before it ever goes near a service start path.
Type=notify and an appropriate, narrow NotifyAccess.--ready only after its real preparation work succeeds.systemctl status, is-active and the journal confirm the expected path.--no-block, --pid= and --exec can change attribution or timing.