Systemd shutdown units are the unsung heroes of clean system shutdowns, but they’re often overlooked until a critical cleanup task fails to execute.

Imagine you have a service that needs to gracefully stop, perhaps by flushing its internal buffers to disk or notifying a central registry. If the system just yanks the power, that data might be lost. Systemd shutdown units, specifically those of type .service with DefaultDependencies=no and Before=shutdown.target, are designed to run before the system starts aggressively shutting down services, giving your custom cleanup logic a fighting chance.

Let’s say you’ve built a custom application, my-data-flusher, that needs to save its state before the system powers off. You’d create a systemd service file for it.

# /etc/systemd/system/my-data-flusher.service
[Unit]
Description=My Data Flusher Service
DefaultDependencies=no
Before=shutdown.target
Wants=shutdown.target

[Service]
Type=oneshot
ExecStop=/usr/local/bin/my-data-flusher --flush-and-exit
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

Here’s what’s happening under the hood:

  • Description: Standard human-readable description.
  • DefaultDependencies=no: This is crucial. By default, systemd adds dependencies that ensure a safe boot and shutdown. For a shutdown unit, we don’t want these, as they might pull in shutdown.target itself or other things that could interfere with our custom ordering. We want to explicitly control when it runs relative to shutdown.target.
  • Before=shutdown.target: This tells systemd that my-data-flusher.service must start before shutdown.target begins its work. This is the primary mechanism for ensuring our cleanup runs early.
  • Wants=shutdown.target: While Before dictates ordering, Wants indicates a looser dependency. It means if shutdown.target is activated, it should also try to activate my-data-flusher.service. This is a common pattern for shutdown hooks.
  • Type=oneshot: This service type is ideal for scripts that perform a single action and then exit. Systemd considers the service active until ExecStop (or ExecStart if RemainAfterExit=yes is not set) completes.
  • ExecStop=/usr/local/bin/my-data-flusher --flush-and-exit: This is the command that will be executed when systemd decides to stop this service, which, due to Before=shutdown.target, will be during the shutdown sequence. We’re assuming our my-data-flusher script is designed to perform its cleanup and exit.
  • RemainAfterExit=yes: For oneshot services, this tells systemd that the service should be considered "active" even after ExecStop (or ExecStart) finishes. This is important because systemd’s shutdown process involves stopping services. Without RemainAfterExit=yes, systemd might try to stop a service that has already completed its "stop" action. For a shutdown hook, we want systemd to acknowledge its presence and ensure it’s ordered correctly.
  • WantedBy=multi-user.target: This is for enabling the service to start during a normal boot. While our primary focus is shutdown, a service often needs to be active to do anything, so enabling it to start with the system makes sense.

To make this work, you’d also need the actual script:

#!/bin/bash
# /usr/local/bin/my-data-flusher

case "$1" in
    --flush-and-exit)
        echo "Flushing data..."
        # Simulate flushing data to disk
        sync
        echo "Data flushed. Exiting."
        exit 0
        ;;
    *)
        echo "Usage: $0 --flush-and-exit"
        exit 1
        ;;
esac

And then, enable and start the service (though starting it is only for testing its ExecStart if you had one, our focus is ExecStop during shutdown):

sudo systemctl daemon-reload
sudo systemctl enable my-data-flusher.service

Now, when you issue sudo systemctl poweroff or sudo systemctl reboot, systemd will activate my-data-flusher.service’s ExecStop command before it proceeds to stop shutdown.target and other critical system components.

The real magic of Before=shutdown.target is how it integrates with systemd’s dependency graph. When shutdown.target is activated, systemd looks at all units that are Before it. These units are then activated in reverse topological order. Since my-data-flusher.service has Before=shutdown.target, it will be pulled into this sequence. The DefaultDependencies=no prevents it from accidentally inheriting dependencies that might cause it to be activated too late or not at all.

The trickiest part is often getting the ExecStop command right. It must be a command that exits cleanly. If your cleanup script hangs indefinitely, it will block the entire shutdown process, leading to a hard reset or a system that never actually powers off. Type=oneshot with RemainAfterExit=yes combined with ExecStop is the standard idiom for these types of tasks.

The next hurdle you’ll likely face is dealing with services that don’t respect their stop signals, forcing you to implement more aggressive kill signals or timeouts in your shutdown unit.

Want structured learning?

Take the full Systemd course →