Skip to content

Designing systemd Unit Dependencies and Startup Order

With systemd, you do not simply make Units depend on each other. You separately design which Units should be started together and which one should start first.

The following differences are especially important.

SettingMain role
Wants=Adds the dependency to the set of Units to start. A failure of the dependency is handled relatively loosely
Requires=Adds the dependency to the set of Units to start and treats it as a stronger mandatory relationship
After=Starts the current Unit after the specified Unit
Before=Starts the current Unit before the specified Unit
.targetGroups multiple Units logically

Wants= and Requires= define requirement relationships, while After= and Before= define ordering relationships. For example, specifying only Wants=example.service does not mean that the current Unit will start after example.service has finished. If ordering is also required, add a directive such as After=.

In this article, we create oneshot services for verification on an RHEL 8-compatible Linux system and check their behavior both when dependencies work normally and when they are intentionally made to fail.

First, create Units for checking dependencies, startup order, and targets.

example-fail.service is a dependency that intentionally fails. By referencing the same Unit from both example-wants.service and example-requires.service, we can compare the difference between Wants= and Requires=.

For startup-order verification, we use example-order-base.service, example-after.service, and example-before.service.

In ExecStart= within a Unit file, % is interpreted by systemd as a specifier. Therefore, when passing %s as a printf format specifier, write it as %%s in the Unit file.

Terminal window
sudo tee /etc/systemd/system/example-fail.service >/dev/null <<'EOF'
[Unit]
Description=Example intentionally failing service
[Service]
Type=oneshot
ExecStart=/usr/bin/false
RemainAfterExit=yes
EOF
sudo tee /etc/systemd/system/example-wants.service >/dev/null <<'EOF'
[Unit]
Description=Example service using Wants
Wants=example-fail.service
After=example-fail.service
[Service]
Type=oneshot
ExecStart=/usr/bin/bash -c 'printf "%%s\n" "wants-parent-started" > /var/tmp/example-wants.result'
RemainAfterExit=yes
EOF
sudo tee /etc/systemd/system/example-requires.service >/dev/null <<'EOF'
[Unit]
Description=Example service using Requires
Requires=example-fail.service
After=example-fail.service
[Service]
Type=oneshot
ExecStart=/usr/bin/bash -c 'printf "%%s\n" "requires-parent-started" > /var/tmp/example-requires.result'
RemainAfterExit=yes
EOF
sudo tee /etc/systemd/system/example-order-base.service >/dev/null <<'EOF'
[Unit]
Description=Example ordering base service
[Service]
Type=oneshot
ExecStart=/usr/bin/bash -c 'printf "%%s\n" "base" >> /var/tmp/example-order.log'
RemainAfterExit=yes
EOF
sudo tee /etc/systemd/system/example-after.service >/dev/null <<'EOF'
[Unit]
Description=Example service ordered after base
Wants=example-order-base.service
After=example-order-base.service
[Service]
Type=oneshot
ExecStart=/usr/bin/bash -c 'printf "%%s\n" "after" >> /var/tmp/example-order.log'
RemainAfterExit=yes
EOF
sudo tee /etc/systemd/system/example-before.service >/dev/null <<'EOF'
[Unit]
Description=Example service ordered before base
Wants=example-order-base.service
Before=example-order-base.service
[Service]
Type=oneshot
ExecStart=/usr/bin/bash -c 'printf "%%s\n" "before" >> /var/tmp/example-order.log'
RemainAfterExit=yes
EOF
sudo tee /etc/systemd/system/example-unordered.service >/dev/null <<'EOF'
[Unit]
Description=Example service with dependency but no explicit ordering
Wants=example-order-base.service
[Service]
Type=oneshot
ExecStart=/usr/bin/true
RemainAfterExit=yes
EOF
sudo tee /etc/systemd/system/example-target-a.service >/dev/null <<'EOF'
[Unit]
Description=Example target member A
[Service]
Type=oneshot
ExecStart=/usr/bin/bash -c 'printf "%%s\n" "target-a-started" > /var/tmp/example-target-a.result'
RemainAfterExit=yes
EOF
sudo tee /etc/systemd/system/example-target-b.service >/dev/null <<'EOF'
[Unit]
Description=Example target member B
[Service]
Type=oneshot
ExecStart=/usr/bin/bash -c 'printf "%%s\n" "target-b-started" > /var/tmp/example-target-b.result'
RemainAfterExit=yes
EOF
sudo tee /etc/systemd/system/example-stack.target >/dev/null <<'EOF'
[Unit]
Description=Example grouped services
Wants=example-target-a.service example-target-b.service
EOF
sudo systemctl daemon-reload

After modifying Unit files, use systemctl daemon-reload to make the systemd manager reload them.

Before running startup tests, inspect the Unit files with systemd-analyze verify.

Terminal window
sudo systemd-analyze verify \
/etc/systemd/system/example-fail.service \
/etc/systemd/system/example-wants.service \
/etc/systemd/system/example-requires.service \
/etc/systemd/system/example-order-base.service \
/etc/systemd/system/example-after.service \
/etc/systemd/system/example-before.service \
/etc/systemd/system/example-unordered.service \
/etc/systemd/system/example-target-a.service \
/etc/systemd/system/example-target-b.service \
/etc/systemd/system/example-stack.target

If errors are displayed, correct the Unit file definitions before testing the dependencies.

Step 3: Check the dependencies recognized by systemd

Section titled “Step 3: Check the dependencies recognized by systemd”

Rather than looking only at what is written in the files, check the dependencies that systemd actually recognizes after reloading them.

Terminal window
sudo systemctl show example-wants.service example-requires.service example-after.service example-before.service example-unordered.service \
-p Id -p Wants -p Requires -p After -p Before --no-pager
sudo systemctl show example-wants.service -p Wants --value | grep -qw 'example-fail.service'
sudo systemctl show example-wants.service -p After --value | grep -qw 'example-fail.service'
sudo systemctl show example-requires.service -p Requires --value | grep -qw 'example-fail.service'
sudo systemctl show example-requires.service -p After --value | grep -qw 'example-fail.service'
sudo systemctl show example-after.service -p After --value | grep -qw 'example-order-base.service'
sudo systemctl show example-before.service -p Before --value | grep -qw 'example-order-base.service'

Here, verify that Wants= and After= are recognized for example-wants.service, and that Requires= and After= are recognized for example-requires.service.

Step 4: Check behavior when a Wants dependency fails

Section titled “Step 4: Check behavior when a Wants dependency fails”

First, test the configuration that uses Wants=.

The dependency example-fail.service always fails, but example-wants.service is configured so that its own startup processing can still run after that failure.

Terminal window
sudo systemctl stop example-wants.service example-fail.service
sudo systemctl reset-failed example-wants.service example-fail.service
sudo rm -f /var/tmp/example-wants.result
sudo systemctl start example-wants.service

Check the state of the requesting Unit and the dependency using properties maintained by systemd itself.

Terminal window
sudo test "$(sudo systemctl show example-wants.service -p ActiveState --value)" = "active"
sudo test "$(sudo systemctl show example-wants.service -p Result --value)" = "success"
sudo test "$(sudo systemctl show example-fail.service -p ActiveState --value)" = "failed"
sudo test "$(sudo systemctl show example-fail.service -p Result --value)" = "exit-code"
sudo systemctl show example-wants.service example-fail.service \
-p Id -p ActiveState -p SubState -p Result --no-pager

If example-fail.service is failed while example-wants.service is active with Result=success, this confirms that failure of a dependency added to the startup set with Wants= does not necessarily prevent the requesting Unit from starting successfully.

Because After=example-fail.service is also specified here, processing of the requesting Unit proceeds after the dependency’s startup job has completed.

Step 5: Check behavior when a Requires dependency fails

Section titled “Step 5: Check behavior when a Requires dependency fails”

Next, reference the same failing Unit with Requires= and After=.

Terminal window
sudo systemctl stop example-requires.service example-fail.service
sudo systemctl reset-failed example-requires.service example-fail.service
sudo rm -f /var/tmp/example-requires.result
sudo systemctl start example-requires.service

This startup is expected to fail because the dependency fails.

Also verify that the requesting Unit’s processing did not run.

Terminal window
sudo systemctl show example-requires.service example-fail.service \
-p Id -p ActiveState -p SubState -p Result --no-pager
sudo test ! -e /var/tmp/example-requires.result

Requires= alone does not define startup order. Because After=example-fail.service is also specified in this example, the mandatory dependency attempts to start first and fails, and that failure is then reflected in the startup of the requesting Unit.

This is why requirement relationships and ordering relationships need to be considered separately.

Now verify the ordering defined by After=.

example-after.service adds example-order-base.service to the same startup transaction with Wants=, and additionally uses After= to specify that it should begin only after startup of the base Unit has completed.

Terminal window
sudo systemctl stop example-after.service example-order-base.service
sudo rm -f /var/tmp/example-order.log
sudo systemctl start example-after.service

Compare the monotonic timestamps recorded by systemd. Confirm that the main process of the after Unit did not start before the base Unit transitioned to active.

Terminal window
base_active="$(sudo systemctl show example-order-base.service -p ActiveEnterTimestampMonotonic --value)"
after_start="$(sudo systemctl show example-after.service -p ExecMainStartTimestampMonotonic --value)"
sudo test -n "$base_active"
sudo test -n "$after_start"
sudo test "$base_active" -le "$after_start"
sudo systemctl show example-order-base.service example-after.service \
-p Id -p ActiveState -p ActiveEnterTimestampMonotonic -p ExecMainStartTimestampMonotonic --no-pager

If base_active is less than or equal to after_start, this confirms that execution of example-after.service began after example-order-base.service entered its completed startup state.

After= defines which Unit is processed first when the other Unit is also part of the startup operation. In this example, Wants= adds the dependency to the startup set, and After= determines the order between the two.

Step 7: Specify the reverse startup order with Before

Section titled “Step 7: Specify the reverse startup order with Before”

Next, specify Before= relative to the same base Unit.

Terminal window
sudo systemctl stop example-before.service example-order-base.service
sudo rm -f /var/tmp/example-order.log
sudo systemctl start example-before.service

Verify the Before= ordering using the monotonic timestamps recorded by systemd. Confirm that the main process of the base Unit starts only after example-before.service has transitioned to active.

Terminal window
before_active="$(sudo systemctl show example-before.service -p ActiveEnterTimestampMonotonic --value)"
base_start="$(sudo systemctl show example-order-base.service -p ExecMainStartTimestampMonotonic --value)"
sudo test -n "$before_active"
sudo test -n "$base_start"
sudo test "$before_active" -le "$base_start"
sudo systemctl show example-before.service example-order-base.service \
-p Id -p ActiveState -p ActiveEnterTimestampMonotonic -p ExecMainStartTimestampMonotonic --no-pager

If before_active is less than or equal to base_start, this confirms that execution of example-order-base.service began after startup processing of example-before.service had completed.

Before= represents an ordering relationship in the opposite direction from After=.

When reading dependencies, if Unit A contains Before=B, it is useful to interpret that as “place A before B.”

Step 8: Verify that Wants alone does not specify startup order

Section titled “Step 8: Verify that Wants alone does not specify startup order”

example-unordered.service has only the following dependency configured.

Wants=example-order-base.service

Neither After= nor Before= is specified.

Start both Units and check the relationship recognized by systemd.

Terminal window
sudo systemctl stop example-unordered.service example-order-base.service
sudo rm -f /var/tmp/example-order.log
sudo systemctl start example-unordered.service
sudo systemctl is-active --quiet example-unordered.service
sudo systemctl is-active --quiet example-order-base.service
sudo systemctl show example-unordered.service -p Wants -p After -p Before --no-pager
sudo systemctl show example-unordered.service -p Wants --value | grep -qw 'example-order-base.service'
if sudo systemctl show example-unordered.service -p After --value | grep -qw 'example-order-base.service'; then
exit 1
fi
if sudo systemctl show example-unordered.service -p Before --value | grep -qw 'example-order-base.service'; then
exit 1
fi

In this state, both Units are included in the startup set, but there is no explicit After= or Before= relationship between example-unordered.service and example-order-base.service.

Therefore, it is incorrect to use Wants= as a setting meaning “start the dependency first.” If ordering is required, design After= or Before= separately from the requirement relationship.

Step 9: Group multiple Units with a target

Section titled “Step 9: Group multiple Units with a target”

When multiple related services need to be started together, a .target Unit can be used as a grouping unit.

In this example, example-stack.target includes the following two services with Wants=.

  • example-target-a.service
  • example-target-b.service

Start only the target and verify that both services are started.

Terminal window
sudo systemctl stop example-stack.target example-target-a.service example-target-b.service
sudo rm -f /var/tmp/example-target-a.result /var/tmp/example-target-b.result
sudo systemctl start example-stack.target
sudo systemctl is-active --quiet example-stack.target
sudo systemctl is-active --quiet example-target-a.service
sudo systemctl is-active --quiet example-target-b.service
sudo test -f /var/tmp/example-target-a.result
sudo test -f /var/tmp/example-target-b.result
sudo cat /var/tmp/example-target-a.result
sudo cat /var/tmp/example-target-b.result

If both target-a-started and target-b-started can be confirmed, the configuration that starts multiple Units together from a single target is working.

With systemd dependencies, it is important to separate the questions “should this Unit be started?” and “when should this Unit be started?”

Typically, if you want a dependency to start together with the requesting Unit but do not want its failure to directly cause the requesting Unit’s startup to fail, consider a combination such as the following.

Wants=example.service
After=example.service

If successful startup of the dependency is required as a condition for starting the requesting Unit, combine a stronger requirement relationship with an ordering relationship.

Requires=example.service
After=example.service

On the other hand, if you only want to specify the order, you can also use only After= or Before= without adding a requirement relationship. In that case, the ordering directive alone does not automatically start the other Unit. Both Units must become part of the startup operation through another dependency or mechanism.

When multiple Units should be treated as a single functional group, using a target can express the intended configuration more clearly than an operational procedure that directly starts individual services one after another.

The basic principle for keeping systemd Unit configurations predictable is not to treat Wants=, Requires=, After=, and Before= as the same kind of “dependency setting,” but to design requirement relationships and ordering relationships separately.

Category: Linux