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