1. Overview

The systemd suite of software tools is well-known for initializing and managing services on a Linux system. With it, services may depend on other resources or services to run properly. Fortunately, we can define dependencies and conditions in the unit configuration file of the service.

This tutorial will examine the options we have to set such prerequisites for a systemd service.

2. Example Service

First, we’ll create a test systemd service that starts an Nginx server Docker container. Our service will depend on the Docker service.

Let’s create a shell script /usr/local/bin/nginx-d.sh that runs the Nginx container:

#!/bin/bash
docker container run -p 80:80 --rm --name nginx-service nginx:latest

The above script will create a new Docker container from the latest Nginx image:

  • -p exports the container port 80 to the host port 80
  • –name sets the container’s name to nginx-service
  • –rm removes the container after we stop it

Notably, we should add the execute permission to the script.

Next, we’ll create a simple unit configuration for our service. We can save it in the /etc/systemd/system/nginx-d.service file:

[Unit]
Description = Nginx Docker Container Service
[Service]
ExecStart = nginx-d.sh
[Install]
WantedBy = multi-user.target

As can be seen, the file has three sections:

  • Unit, with the description of the service
  • Service, with the executable in the ExecStart option
  • Install, where we set the WantedBy option to the multi-user.target value

Importantly, WantedBy defines a time during the system’s boot process when we want to start our service. The multi-user.target value refers to the time when the system is ready to accept multiple command-line user sessions.

Moreover, our service will run under the root user. This is the default setting.

3. Enabling and Testing the Example Service

Next, let’s reload the configuration and enable the service:

$ sudo systemctl daemon-reload
$ sudo systemctl enable nginx-d
Created symlink ...

The daemon-reload subcommand reloads all unit configurations of systemctl. In addition, the enable subcommand will make the service start at the next system boot.

So, after restarting our system, let’s verify that our service has started by sending a request with the curl command:

$ curl http://localhost
<!DOCTYPE html>
<html>
<head>
<title> Welcome to nginx! </title>
$ sudo systemctl status nginx-d
● nginx-d.service - Nginx Docker Container Service
     Loaded: loaded (... enabled; vendor preset: enabled)
     Active: active (running) since Thu 2023-01-19 16:19:38 EET; 19min ago
...

Indeed, an Nginx server instance is running as the above HTTP response confirms. Moreover, we can check the status of our service with the systemctl status command to verify that the service is active.

4. The Requires Option

We can specify that our service depends on another service with the Requires option.

Upon system start-up, systemd will check if the dependency is active and attempt to activate it if not. In case the dependency fails, systemd will not start our service. We populate the Requires option under the Unit section:

[Unit]
Description = Nginx Docker Container Service
Requires = docker.service

Here, we set our service to depend on the Docker service to run.

Now, we’ll make the Docker service fail during system start-up and see what happens with our own. After a reboot, let’s check the status of our nginx-d service:

$ sudo systemctl status nginx-d
● nginx-d.service - Nginx Docker Container Service
     Loaded: loaded ...
     Active: inactive (dead) since Fri 2023-01-20 10:08:55 EET; 21s ago
    ...
Jan 20 10:08:55 systemd[1]: Dependency failed for Nginx Docker Container Service.
Jan 20 10:08:55 systemd[1]: nginx-d.service: Job nginx-d.service/ start failed with result 'dependency'.

Indeed, we can see in the logs that systemd eventually stopped our service due to a failed dependency.

5. Service Conditions

We can add some condition options under the Unit section. The systemd manager checks our conditions before it starts the service and tries to activate the service only if the conditions are true.

For example, we can specify a number of conditions related to many aspects of a system:

  • architecture
  • execution environment
  • filesystem
  • availability of resources

Let’s see some examples.

5.1. Check if a Path Exists

The ConditionPathExists option checks if a path exists in the local filesystem. Let’s add a check for a file that doesn’t exist:

[Unit]
Description = Nginx Docker Container Service
ConditionPathExists = /opt/non-existant.txt

In the above example, the /opt/non-existant.txt file doesn’t exist, so the service shouldn’t be started:

$ sudo systemctl status nginx-d
● nginx-d.service - Nginx Docker Container Service
     Loaded: loaded (... enabled; vendor preset: enabled)
     Active: inactive (dead)
  Condition: start condition failed at Fri 2023-01-20 11:18:14 EET; 2s ago
             └─ ConditionPathExists=/opt/non-existant.txt was not met
Jan 20 11:12:11 ... systemd[1]: Condition check resulted in Nginx Docker Container Service being skipped.

As we expected, the service is inactive due to the unmet condition.

5.2. Check if a File Is Empty

We can check if a file is empty with the ConditionFileNotEmpty. Let’s try this in the previous example to see what happens:

[Unit]
Description = Nginx Docker Container Service
ConditionPathExists = /opt/empty.txt
ConditionFileNotEmpty = /opt/empty.txt

Now, let’s reload the configuration and create the file:

$ sudo systemctl daemon-reload
$ sudo touch /opt/empty.txt

So, we have an empty file to check. Let’s restart the service and check its status:

$ sudo systemctl restart nginx-d
$ sudo systemctl status nginx-d | grep 'Active\|Condition'
Active: inactive (dead) since Fri 2023-01-20 11:46:20 EET; 28min ago
Condition: start condition failed on Fri 2023-01-20 12:10:34 EET; 4min 11s ago
└─ ConditionFileNotEmpty = /opt/empty.txt was not met

Consequently, the service didn’t start. The output states that the file-not-empty condition failed. Both of the conditions must be true to run the service.

5.3. The Exclamation Mark Operator

We can use the exclamation mark operator to negate a condition. Let’s negate one condition of the previous example:

[Unit]
Description = Nginx Docker Container Service
ConditionPathExists = /opt/empty.txt
ConditionFileNotEmpty =! /opt/empty.txt

Now, the service will be started since the /opt/empty.txt file is empty:

$ sudo systemctl daemon-reload
$ sudo systemctl status nginx-d | grep 'Active\|Condition'
     Active: active (running) since Fri 2023-01-20 12:22:25 EET; 4s ago

As expected, the service is active.

5.4. Triggering Conditions

Conditions with the pipe operator are called triggering conditions. A service is started if at least one of them is true. Also, they can co-exist with simple conditions. In this case, a service is activated if at least one triggering and all simple conditions are true.

Let’s try the pipe operator:

[Unit]
Description = Nginx Docker Container Service
ConditionPathExists=| /opt/test.txt
ConditionPathExists=| /opt/anothertest.txt
ConditionUser = root
ConditionArchitecture = x86-64

We can see that two of the four conditions now have the pipe operator. This means that at least one of the files test.txt and anothertest.txt should exist.

Moreover, we used two new options:

  • ConditionUser returns true if the service runs under the specified user
  • ConditionArchitecture checks the system’s architecture, in our case x86-64

The service manager will start the service since:

  • file /opt/test.txt exists
  • the service by default runs under the root user
  • the system’s architecture is x86-64

Let’s verify this:

$ sudo systemctl daemon-reload
$ sudo systemctl restart nginx-d
$ sudo systemctl status nginx-d | grep 'Active\|Condition'
Active: active (running) since Fri 2023-01-20 12:55:35 EET; 14min ago

As expected, the service is again active.

6. Service Asserts

Asserts are pretty similar to conditions. However, if an assert fails, it returns an error when we try to start our service. This is in contrast to conditions, which fail silently. Also, there are equivalent asserts for most condition options.

Let’s use an assert to check if a file exists:

[Unit]
Description = Nginx Docker Container Service
AssertPathExists= /opt/non-existant.txt

The file /opt/non-existant.txt doesn’t exist. Thus, we expect that the service manager won’t be able to start the service:

$ sudo systemctl daemon-reload
$ sudo systemctl restart nginx-d
Assertion failed on job for nginx-d.service.
$ echo $?
1

Indeed, we can see that an error message was printed after restarting. Furthermore, the exit status for the restart command is 1.

7. Conclusion

In this article, we’ve learned about the available options of a systemd service file for conditionally starting a service.

First, we created, enabled, and tested an example service. After that, we looked at the Requires option for a way to set dependencies. Later, we explored conditions for services. Finally, we explained what asserts are and how to use them.

Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.