sd_notify is the surprisingly simple way to tell systemd "I’m ready, don’t kill me."

Let’s see it in action. Imagine a basic Python service that needs to signal readiness.

import os
import socket
import time
import sys

def main():
    # Check if the NOTIFY_SOCKET environment variable is set
    notify_socket = os.environ.get('NOTIFY_SOCKET')
    if not notify_socket:
        print("NOTIFY_SOCKET not set, not signaling readiness.", file=sys.stderr)
        # In a real service, you might still continue, but without signaling.
        # For this example, we'll exit to show the intended flow.
        sys.exit(1)

    try:
        # Connect to the notification socket
        sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
        sock.connect(notify_socket)

        # Signal readiness
        message = "READY=1"
        sock.sendall(message.encode('utf-8'))
        print(f"Sent: {message}")

        # Keep the service running
        print("Service is running and signaled readiness. Press Ctrl+C to stop.")
        while True:
            time.sleep(1)

    except ConnectionRefusedError:
        print(f"Connection refused to {notify_socket}. Is systemd running and configured correctly?", file=sys.stderr)
        sys.exit(1)
    except Exception as e:
        print(f"An error occurred: {e}", file=sys.stderr)
        sys.exit(1)
    finally:
        if 'sock' in locals() and sock:
            sock.close()

if __name__ == "__main__":
    main()

Now, how do we tell systemd to listen to this signal? In your service’s .service file, you need two key directives: NotifyAccess=all and StandardUnit=your-service-name.service. The NotifyAccess directive allows the service to send notifications to systemd, and StandardUnit ensures systemd knows this service is managed by it.

[Unit]
Description=My Ready Service
After=network.target

[Service]
ExecStart=/usr/bin/python3 /path/to/your/service.py
Restart=on-failure
NotifyAccess=all
StandardUnit=my-ready-service.service # Important for systemd to associate the notify socket

[Install]
WantedBy=multi-user.target

When systemd starts my-ready-service.service, it creates a unique Unix domain socket and sets the NOTIFY_SOCKET environment variable for the service process to that socket’s path. The service, upon receiving this variable, connects to it and sends READY=1. systemd monitors this socket; when it receives READY=1, it marks the service as "active (running)" and proceeds to start any services that depend on it. If the service crashes before sending READY=1, systemd will consider it failed and restart it (if Restart=on-failure is set).

The beauty of sd_notify is that it decouples the service’s startup logic from systemd’s management. The service doesn’t need to know how systemd is waiting; it just needs to send a standardized message. This allows systemd to manage dependencies more intelligently. For instance, if a web server signals READY=1, systemd knows it can start routing traffic to it. If a database signals READY=1, other applications can be sure it’s ready to accept connections.

Beyond READY=1, sd_notify supports other messages. STATUS= can provide a human-readable status string that appears in systemctl status. RELOADING=1 can signal that the service is performing a configuration reload without a full restart. STOPPING=1 can be sent by the service itself just before it exits cleanly, allowing systemd to transition it to the "deactivating" state gracefully.

A common point of confusion is when NotifyAccess is not set or is set incorrectly. If NotifyAccess is missing or set to main (the default, which only allows notifications from the main process, not child processes), and your service forks, systemd will never receive the notification from the child process, leading to the service appearing as "activating" indefinitely. Setting NotifyAccess=all grants permission to any process spawned by the service to send notifications, which is usually what you want for robust readiness signaling.

The StandardUnit directive isn’t strictly necessary for sd_notify to work, but it’s crucial for systemd’s internal state management and for correctly associating the notification socket with the unit. Without it, systemd might not correctly update the unit’s state or track dependencies that rely on readiness.

Many services that use sd_notify also implement a heartbeat mechanism by sending STATUS= messages periodically. This tells systemd that the service is not only running but also actively processing. If the service stops sending these heartbeats, systemd might eventually consider it stale and restart it, depending on its configuration.

The primary benefit of sd_notify over older methods like relying on PID files or simple socket listening checks is its atomicity and reliability. sd_notify guarantees that the service has reached a specific, defined state of readiness, not just that a process is running or a port is open. This prevents race conditions where a service might be marked as ready before it’s truly capable of handling requests.

The next concept you’ll likely encounter is how to integrate sd_notify with more complex application lifecycles, such as graceful shutdown sequences and handling reload signals.

Want structured learning?

Take the full Systemd course →