Systemd Service Stop Order Management: Ensuring Proper Dependency Shutdown Sequence


2 views

When working with systemd services that depend on mounted filesystems, we often encounter a critical sequencing challenge: while startup dependencies are well-documented, shutdown order requirements are less straightforward. The inverse dependency pattern becomes crucial - services using resources must stop before the resource provider (like our mount service) terminates.

Systemd manages shutdown order through several key unit file directives:

Before=         # Specifies units that should be stopped after this unit
After=          # Specifies units that should be stopped before this unit
Conflicts=      # Units that cannot run simultaneously
Requires=       # Hard dependencies (stops together)
Wants=          # Soft dependencies

The crucial insight is that After= and Before= work in reverse during shutdown - units listed in After= will be stopped before the current unit during shutdown.

For our example with mountCustomPartitions.service, ProgramA.service, and ProgramB.service, we need these shutdown behaviors:

  • ProgramB must stop before ProgramA
  • Both programs must stop before mountCustomPartitions
  • Stopping mountCustomPartitions should trigger other services to stop

Here's the corrected configuration:

[Unit]
Description=My Custom Partition Mounting Service
Before=ProgramA.service
Conflicts=ProgramA.service ProgramB.service

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/mountCustomPartitions.sh mount
ExecStop=/usr/bin/mountCustomPartitions.sh unmount

[Install]
WantedBy=multi-user.target
[Unit]
Description=My Generic Program A Service
Wants=mountCustomPartitions.service
After=mountCustomPartitions.service
Before=ProgramB.service
Conflicts=ProgramB.service

[Service]
Type=simple
ExecStart=/usr/bin/ProgramA

[Install]
WantedBy=multi-user.target

For more complex scenarios with multiple dependent services, consider these approaches:

[Unit]
# For services that should stop in sequence
Before=service1.service service2.service
Conflicts=service1.service service2.service

# Explicit stop ordering via drop-in files
# Create /etc/systemd/system/mountCustomPartitions.service.d/10-shutdown-order.conf
[Unit]
StopWhenUnneeded=yes

After implementing these changes, verify the shutdown order with:

systemd-analyze verify /etc/systemd/system/*.service
systemd-analyze dot | dot -Tsvg > dependencies.svg

Monitor shutdown behavior with:

journalctl -u mountCustomPartitions -u ProgramA -u ProgramB -f

For systems with many dependent services, creating a dedicated target unit can provide cleaner management:

[Unit]
Description=Services Using Custom Mounts
Before=mountCustomPartitions.service
Conflicts=mountCustomPartitions.service
Wants=ProgramA.service ProgramB.service

Then modify your services to PartOf=custom-mounts.target and WantedBy=custom-mounts.target.


When dealing with complex service dependencies in systemd, the stop ordering becomes particularly important for services that rely on mounted filesystems. The key directives we'll examine are:

[Unit]
# Startup order (traditional approach)
After=mountCustomPartitions.service

# Shutdown order (what we need)
Before=mountCustomPartitions.service

Here's how to properly modify your service files to ensure correct stop ordering:

# ProgramB.service - Updated with proper stop ordering
[Unit]
Description=My Generic Program B Service
Requires=ProgramA.service
Wants=mountCustomPartitions.service
After=mountCustomPartitions.service ProgramA.service
Before=mountCustomPartitions.service

[Service]
Type=simple
ExecStart=/usr/bin/ProgramB

[Install]
WantedBy=multi-user.target

For even better control, we can use PartOf to create a dependency chain where stopping the mount service automatically stops dependent services:

# ProgramA.service with PartOf directive
[Unit]
Description=My Generic Program A Service
Wants=mountCustomPartitions.service
After=mountCustomPartitions.service
Before=mountCustomPartitions.service
PartOf=mountCustomPartitions.service

[Service]
Type=simple
ExecStart=/usr/bin/ProgramA

[Install]
WantedBy=multi-user.target

For scenarios with multiple interdependent services, we can create a dedicated target unit:

# custom-apps.target
[Unit]
Description=Custom Application Stack
Wants=mountCustomPartitions.service
After=mountCustomPartitions.service
Before=mountCustomPartitions.service
PartOf=mountCustomPartitions.service

Then modify individual services:

# ProgramB.service with target dependency
[Unit]
Description=My Generic Program B Service
Requires=ProgramA.service
After=ProgramA.service
PartOf=custom-apps.target

[Service]
Type=simple
ExecStart=/usr/bin/ProgramB

[Install]
WantedBy=custom-apps.target

Use these commands to verify your dependencies:

systemctl list-dependencies --reverse mountCustomPartitions.service
systemctl show -p Requires,Requisite,BindsTo,PartOf,Before,After ProgramA.service
journalctl -u mountCustomPartitions.service -u ProgramA.service -u ProgramB.service --no-pager -n 50