1. Overview

When the service manager, systemd, starts a service, it needs to know whether the service started successfully. There are several options for services to notify systemd about the completion of their start-up. Service scripts may use the systemd-notify command to notify systemd about status updates.

In this tutorial, we’ll discuss how to use systemd-notify. Firstly, we’ll discuss the need for systemd-notify. Then, we’ll see two examples of how to use the command.

2. What Is systemd-notify?

A service specifies its start-up type using the Type option in its service’s unit file. The default value for the Type option is simple. The service manager assumes that a service completes its start-up as soon as it’s spawned if its type is simple. However, this assumption may not be valid for all services. For example, a service might be ready only after it establishes a connection to a database.

The notify value of the Type option in a unit file is for this kind of service, which isn’t ready when it’s spawned. The service manager expects the service to send a “READY=1” notification message when it’s ready. A service can use the sd_notify() function to send the “READY=1” notification message and other status updates.

If the spawned service is a shell script and its Type option in its unit file is notify, then the service script can send the notification message and status updates using the systemd-notify command. systemd-notify uses the sd_notify() function under the hood.

systemd gets the status notifications of the services it starts using a Unix domain datagram socket, /run/systemd/notify. The services receive the path of this socket file using the NOTIFY_SOCKET environment variable. systemd sets this environment variable for the services it starts.

The shell script started by systemd may spawn other scripts. If we also want these child processes to send status updates using systemd-notify, then we must set the NotifyAccess option in the Service section of the service’s unit file to all, i.e., NotifyAccess=all. Otherwise, systemd rejects the state updates from the child processes.

3. Usage in a Service

We’ll use the following script, shell_daemon.sh, to show the usage of systemd-notify:

#!/bin/bash
 
function handle_signal()
{   systemd-notify --status "Exiting..."
    sleep 10
    exit 0
}
 
trap handle_signal SIGINT
 
systemd-notify --ready --status "Starting..."
sleep 10 

while true
do
    systemd-notify --status "Sleeping..."
    sleep 5
    
    systemd-notify --status "Pinging..."
    timeout 5 ping 240.0.0.0 >& /dev/null
done

Let’s break down the script to analyze it briefly.

3.1. Dissection of the Script

Firstly, we install a signal handler:

function handle_signal() 
{     
    systemd-notify --status "Exiting..."
    sleep 10
    exit 0
} 

trap handle_signal SIGINT

The handle_signal() function is called when we stop the running script using the SIGINT signal thanks to the trap handle_signal SIGINT command. It notifies the service manager using systemd-notify –status “Exiting…”. The script sleeps for 10 seconds using sleep 10 so that we can check the service’s status before it exits using exit 0. The status of the service is “Exiting…” in this interval.

After the script starts running, it first notifies the service manager by invoking systemd-notify:

systemd-notify --ready --status "Starting..."
sleep 10

The script reports that it’s ready and its status “Starting…”. It sleeps for 10 seconds in this state.

Then, the script gets into an infinite while loop:

while true
do
    systemd-notify --status "Sleeping..." 
    sleep 5 

    systemd-notify --status "Pinging..."
    timeout 5 ping 240.0.0.0 >& /dev/null
done

It first reports its status as “Sleeping…” by invoking systemd-notify –status “Sleeping…” within the while loop. Then, after sleeping for 5 seconds using sleep 5, it pings a non-existing IP address, namely 240.0.0.0, by calling timeout 5 ping 240.0.0.0 >& /dev/null. Pinging lasts for 5 seconds since the duration of the timeout command is 5. The state of the service is “Pinging…” in this interval.

3.2. The Service’s Unit File

We’ll use the following unit file, shell_daemon.service, to run the script as a daemon using systemd:

[Unit]
Description=Example script using systemd-notify

[Service] 
ExecStart=/home/alice/work/systemd-notify/shell_daemon.sh
Type=notify

[Install]
WantedBy=multi-user.target

The important part of this unit file for our case is the Type option in the Service section. Setting the Type option to notify means that the service issues notification messages to systemd.

The ExecStart option specifies the path of our script.

3.3. Starting the Service

Now, we’ll start the service using systemd:

$ cd /etc/systemd/system 
$ sudo ln -s /home/alice/work/systemd-notify/shell_daemon.service
$ sudo systemctl enable shell_daemon
Created symlink /etc/systemd/system/multi-user.target.wants/shell_daemon.service → /home/alice/work/systemd-notify/shell_daemon.service. 
$ sudo systemctl start shell_daemon

After creating a symbolic link to the shell_daemon.service unit file in the /etc/systemd/system directory, we enable the service using the sudo systemctl enable shell_daemon command. Finally, we start the service using the sudo systemctl start shell_daemon. These commands require root privileges, so we use the sudo command together with them.

3.4. Checking the Service’s Status

Having started the service, it’s time to check the service’s status using the sudo systemctl status shell_daemon command:

$ sudo systemctl status shell_daemon
● shell_daemon.service - Example script using systemd-notify
     Loaded: loaded (/etc/systemd/system/shell_daemon.service; enabled; vendor preset: enabled)
     Active: active (running) since Thu 2024-03-28 16:09:04 EDT; 2s ago
   Main PID: 6663 (shell_daemon.sh)
     Status: "Starting..."
      Tasks: 2 (limit: 4594)
     Memory: 552.0K 
        CPU: 4ms  
     CGroup: /system.slice/shell_daemon.service  
             ├─6663 /bin/bash /home/alice/work/systemd-notify/shell_daemon.sh  
             └─6665 sleep 10 

Mar 28 16:09:04 ubuntu-2204 systemd[1]: Starting Example script using systemd-notify...
Mar 28 16:09:04 ubuntu-2204 systemd[1]: Started Example script using systemd-notify. 
Mar 28 16:09:04 ubuntu-2204 shell_daemon.sh[6663]: Starting

The service started two seconds ago according to the information in the line starting with Active. The Status field in the output displays the status of the service, which is “Starting…”. The script is executing the sleep 10 command before the while loop.

Let’s check the service’s status again after a while:

$ sudo systemctl status shell_daemon
● shell_daemon.service - Example script using systemd-notify      
     Loaded: loaded (/etc/systemd/system/shell_daemon.service; enabled; vendor preset: enabled) 
     Active: active (running) since Thu 2024-03-28 16:09:04 EDT; 23s ago  
   Main PID: 6663 (shell_daemon.sh)
     Status: "Sleeping..."    
      Tasks: 2 (limit: 4594)    
     Memory: 552.0K    
        CPU: 13ms   
     CGroup: /system.slice/shell_daemon.service     
             ├─6663 /bin/bash /home/alice/work/systemd-notify/shell_daemon.sh   
             └─6684 sleep 5 

Mar 28 16:09:04 ubuntu-2204 systemd[1]: Starting Example script using systemd-notify...
... 

Now, the service is executing the sleep 5 command in the while loop. As expected, its status is “Sleeping…”.

Let’s check its status again after a few seconds:

$ sudo systemctl status shell_daemon 
● shell_daemon.service - Example script using systemd-notify
     Loaded: loaded (/etc/systemd/system/shell_daemon.service; enabled; vendor preset: enabled)
     Active: active (running) since Thu 2024-03-28 16:09:04 EDT; 28s ago 
   Main PID: 6663 (shell_daemon.sh)  
     Status: "Pinging..."   
      Tasks: 3 (limit: 4594)   
     Memory: 844.0K    
        CPU: 17ms
     CGroup: /system.slice/shell_daemon.service    
             ├─6663 /bin/bash /home/alice/work/systemd-notify/shell_daemon.sh  
             ├─6690 timeout 5 ping 240.0.0.0          
             └─6691 ping 240.0.0.0
 
Mar 28 16:09:04 ubuntu-2204 systemd[1]: Starting Example script using systemd-notify...
... 

Now, the service is executing the ping 240.0.0.0 command in the while loop. Its status is “Pinging…”.

Finally, let’s stop the running script using the SIGINT signal, and check the service’s status:

$ sudo kill -SIGINT 6663
$ sudo systemctl status shell_daemon
● shell_daemon.service - Example script using systemd-notify  
     Loaded: loaded (/etc/systemd/system/shell_daemon.service; enabled; vendor preset: enabled)  
     Active: active (running) since Thu 2024-03-28 16:09:04 EDT; 17min ago   
   Main PID: 6663 (shell_daemon.sh)  
     Status: "Exiting..."  
      Tasks: 2 (limit: 4594)   
     Memory: 560.0K    
        CPU: 725ms   
     CGroup: /system.slice/shell_daemon.service     
             ├─6663 /bin/bash /home/alice/work/systemd-notify/shell_daemon.sh     
             └─7294 sleep 10
 
Mar 28 16:25:40 ubuntu-2204 shell_daemon.sh[6663]: Pinging
...

6663 is the PID of the running script. The service is executing the sleep 10 in the handle_signal() function. Its status is “Pinging…” as expected.

4. Usage From the Command Line

In this section, we’ll use systemd-notify to send a notification to a process other than systemd, namely socat. The socat command supports many address types including Unix domain datagram sockets.

Let’s spawn a process using socat:

$ socat UNIX-RECV:/tmp/my.sock -

The socat command expects to get two addresses as arguments. The first address in our example is UNIX-RECV:/tmp/my.sock. The UNIX-RECV address type is for listening on a socket file using a Unix domain datagram socket, which is /tmp/my.sock in our example. It creates the socket file if it doesn’t exist.

The second argument, namely hyphen (), specifies that messages should be read from STDIN and that incoming messages should be written to STDOUT. Therefore, socat establishes a bidirectional communication channel between the two addresses.

Having run the socat UNIX-RECV:/tmp/my.sock – command, socat starts to wait to receive messages from the socket file. Let’s run systemd-notify in another terminal to send the “READY=1” notification and state update to the running socat process:

$ NOTIFY_SOCKET=/tmp/my.sock systemd-notify --ready --status="Greetings from systemd-notify" --no-block

First, we set the NOTIFY_SOCKET environment variable to /tmp/my.sock so that system-notify can use this socket file to send its notifications. We don’t want systemd-notify to synchronously wait for the requested operation to finish, so we use its –no-block option.

Let’s check the output of the running socat process:

$ socat UNIX-RECV:/tmp/my.sock - 
READY=1
STATUS=Greetings from systemd-notify

As is apparent from the output, socat received the start-up information together with the status string sent by systemd-notify.

Therefore, we’re successful in sending notification messages to other processes using systemd-notify by setting the NOTIFY_SOCKET environment variable.

5. Conclusion

In this article, we discussed how to use systemd-notify. Firstly, we learned when we need to use systemd-notify. Then, we learned to use it in a service to notify the service’s state updates to systemd. Finally, we saw how to use it from the command line to send notifications to socat.