Mastering systemd: Creating Your Own Linux Service

Date: 29/07/2025

Despite numerous challenges and mixed feelings from users, systemd has already become the de facto standard in most Linux distributions. With it, you can create a simple service in just a few minutes and with about ten lines of configuration. However, many of its more intriguing features are poorly documented or require an in-depth understanding of systemd’s intricacies.

Basics

If you’ve never created your own services before, let’s start with the basics. Systemd operates with abstract units, which come in different types, can provide various resources (such as processes, sockets, and abstract “targets”), and may require other resources to start.

The most common type of resource is a service. Files describing services and other related components are located in the /lib/systemd/system/ directory. To allow systemd to recognize a new service, you simply need to place your file in this directory. If the service did not previously exist, systemd will read the file and load it into memory. However, if you edit the file of an already running service, remember to have systemd re-read the files using the command sudo systemctl daemon-reload!

Oneshot Services: Farewell to rc.local

At one time, the standard way to add command execution during system startup was to append them to /etc/rc.local. The obvious drawback of this method is the lack of ways to track their execution success. With systemd, it’s easy to create a custom oneshot service for this purpose, which can be managed through systemctl like any other service. In this case, systemd will execute the command and consider the service startup successful if it exits with a zero code.

Save the following file to /lib/systemd/system/dumb-test.service:

[Unit]
Description=Dumb test
[Service]
ExecStart=/bin/true
Type=oneshot
[Install]
WantedBy=multiuser.target

No additional actions are needed, and now you can manage it just like system services: start it with sudo systemctl start dumb-test.service, enable it to run at boot with sudo systemctl enable dumb-test.service, and so on.

Turning Any Program into a Service

Any long-running process can be easily turned into a service using the Type=idle option. In this case, systemd will intercept standard input/output streams and monitor the process’s lifecycle.

To demonstrate, let’s write a Python program that simply outputs a message in an infinite loop. Save the following script in /usr/local/bin/test.py:

import time
while True:
print("Test service is alive")
time.sleep(5)

Next, create a service file for it at /lib/systemd/system/smart-test.service:

[Unit]
Description=Smart test
[Service]
ExecStart=/usr/bin/python3 -u /usr/local/bin/test.py
Type=idle
KillMode=process
SyslogIdentifier=smart-test
SyslogFacility=daemon
Restart=on-failure
[Install]
WantedBy=multiuser.target

Now you can start our service and verify that it’s working:

$ sudo systemctl status smart-test.service
smart-test.service - Smart test
Loaded: loaded (/usr/lib/systemd/system/smart-test.service; static; vendor preset: disabled)
Active: active (running) since Fri 2019-10-25 16:25:18 +07; 1s ago
Main PID: 19893 (python3)
Tasks: 1 (limit: 4915)
Memory: 3.5M
CGroup: /system.slice/smart-test.service
└─19893 /usr/bin/python3 -u /usr/local/bin/test.py

The program’s standard output is written to journald, and you can view it using journalctl -u smart-test. Out of curiosity, let’s take a look at how the Restart=on-failure option works. We’ll stop our process using kill -9 ${Main PID} and check the logs:

systemd[1]: Started Smart test.
Test service is alive
Test service is alive
smart-test.service: Main process exited, code=killed, status=9/KILL
smart-test.service: Failed with result ‘signal’.
smart-test.service: Service RestartSec=100ms expired, scheduling restart.
smart-test.service: Scheduled restart job, restart counter is at 1.
Stopped Smart test.
Started Smart test.
Test service is alive

For true demons, you should use the forking type, but we won’t go into details here — the authors of such packages likely already know what to do.

Dependencies and Startup Order

There are numerous options for configuring dependencies in systemd. It’s important to highlight that it has two separate mechanisms for specifying the order of service startup and the dependencies between them.

Service Startup Order

The order in which services are started is determined by the Before and After options. If the configuration for the service foo specifies After=bar.service and both services need to be started, systemd will first attempt to start bar and then foo.

However, the option After=bar.service by itself will not enable the service to start. Moreover, it will not influence the decision to run foo, even if the start of bar fails.

The reason for these options is systemd’s ability to start services in parallel.

Let’s consider a typical web server setup with an FCGI web application, a database management system, and a reverse proxy. The order in which you start the FCGI process and the reverse proxy is not crucial. Requests will function only when both are running, but an “incorrect order” won’t prevent them from starting properly.

If a web application requires data from a database for initialization, it’s not enough to just ensure that both the FCGI process and the database management system (DBMS) are running. You must start the application only after the DBMS has fully launched. This is where the Before/After options come into play.

Dependencies

Dependencies come in two types: soft and hard. If both services start successfully, there is no difference between them. The distinction becomes relevant if one of the services fails to start: with a soft dependency, the dependent services will still be launched, whereas with a hard dependency, systemd won’t even attempt to start them.

Soft dependencies are specified using the Wants= option in the [Unit] section. Here’s an example from sshd.service:

[Unit]
Description=OpenSSH server daemon
Documentation=man:sshd(8) man:sshd_config(5)
After=network.target sshd-keygen.target
Wants=sshd-keygen.target

The sshd-keygen.target evidently serves to generate a host key if it is missing. Technically, sshd cannot start without a host key, so why the authors decided to make this a non-mandatory dependency is anyone’s guess. Perhaps they assumed that in most cases, the key already exists and that an outdated key is better than a non-functional SSH.

info

When you copy settings from distribution packages, be cautious. Distribution maintainers are only human, and they can ship sub‑optimal—or outright incorrect—configurations. Plus, what works for one service won’t necessarily work for another, so cross‑check with the documentation and run tests before you ship.

You can specify strict dependencies using the Requires option. For example, in the systemd-journal-flush.service, there’s an option Requires=systemd-journald.service — it’s evident that sending a journald command is impossible until it’s running.

There are variations of this option, for example, RequiresMountsFor. Let’s take a look at the logrotate.service file:

[Unit]
Description=Rotate log files
Documentation=man:logrotate(8) man:logrotate.conf(5)
RequiresMountsFor=/var/log

To function properly, logrotate only needs access to the directory containing the logs, and nothing else. The RequiresMountsFor=/var/log option ensures this by allowing the service to start as soon as the directory containing the /var/log path is mounted, even if it isn’t located in the root partition.

Integrating into Dependencies of External Services

In systems using System V init, adding dependencies to someone else’s service required editing its script. Such changes, obviously, wouldn’t survive a system update, so the only way to make them permanent would be to rebuild the package.

In systemd, there are a few ways to tackle this issue. If you specifically need to create a dependency on another service, you might consider using reverse dependency options: WantedBy and RequiredBy. These should be placed in the [Install] section, not the [Unit] section. There are two caveats: these options are only processed when a service is installed with systemctl enable and, as with everything in systemd, they may not function consistently across all versions.

The second option, which allows you to change any settings, is to copy the service file to /etc/systemd/system/. If a file exists in both directories, the file from /etc/systemd/system takes precedence.

A third, less radical option is to create a file like /etc/systemd/system/${unit}.d/local.conf and include only the necessary configurations there.

As an example, let’s pretend that our smart-test service isn’t actually ours, and let’s add a dependency on sshd.service using the third method. Create a file at /etc/systemd/system/smart-test.service.d/local.conf with the following content:

[Unit]
Requires=sshd.service

Now, the sshd.service will start alongside the smart-test.service, even if it was previously turned off.

If the order of launching the two services is important in addition to the fact that they’re both running, make sure to specify this using the Before or After options.

Default Dependencies

It’s important to note that systemd automatically creates dependencies for each service by default. In most cases, this is beneficial because user services typically require a fully initialized system to function properly. However, if you want to introduce a new step in the system boot process, you’ll need to remove these dependencies. This can be achieved by using the DefaultDependencies=no option.

This template works well for services that need to start as early as possible:

[Unit]
Description=My early boot step
DefaultDependencies=no
After=systemd-remount-fs.service

To review a service’s dependencies and ensure there are no unnecessary ones, you can use the command systemctl list-dependencies ${unit}.

Conclusion

Systemd is something you can love or hate, but you can’t ignore it—you need to know how to work with it. Hopefully, this knowledge will help you turn systemd to your advantage.

Related posts:
2023.04.19 — Kung fu enumeration. Data collection in attacked systems

In penetration testing, there's a world of difference between reconnaissance (recon) and data collection (enum). Recon involves passive actions; while enum, active ones. During recon,…

Full article →
2022.06.01 — Log4HELL! Everything you must know about Log4Shell

Up until recently, just a few people (aside from specialists) were aware of the Log4j logging utility. However, a vulnerability found in this library attracted to it…

Full article →
2023.07.07 — VERY bad flash drive. BadUSB attack in detail

BadUSB attacks are efficient and deadly. This article explains how to deliver such an attack, describes in detail the preparation of a malicious flash drive required for it,…

Full article →
2023.02.21 — Herpaderping and Ghosting. Two new ways to hide processes from antiviruses

The primary objective of virus writers (as well as pentesters and Red Team members) is to hide their payloads from antiviruses and avoid their detection. Various…

Full article →
2023.06.08 — Cold boot attack. Dumping RAM with a USB flash drive

Even if you take efforts to protect the safety of your data, don't attach sheets with passwords to the monitor, encrypt your hard drive, and always lock your…

Full article →
2022.02.09 — Dangerous developments: An overview of vulnerabilities in coding services

Development and workflow management tools represent an entire class of programs whose vulnerabilities and misconfigs can turn into a real trouble for a company using such software. For…

Full article →
2023.02.21 — SIGMAlarity jump. How to use Sigma rules in Timesketch

Information security specialists use multiple tools to detect and track system events. In 2016, a new utility called Sigma appeared in their arsenal. Its numerous functions will…

Full article →
2022.06.01 — First contact. Attacks on chip-based cards

Virtually all modern bank cards are equipped with a special chip that stores data required to make payments. This article discusses fraud techniques used…

Full article →
2023.02.21 — Pivoting District: GRE Pivoting over network equipment

Too bad, security admins often don't pay due attention to network equipment, which enables malefactors to hack such devices and gain control over them. What…

Full article →
2023.03.26 — Attacks on the DHCP protocol: DHCP starvation, DHCP spoofing, and protection against these techniques

Chances are high that you had dealt with DHCP when configuring a router. But are you aware of risks arising if this protocol is misconfigured on a…

Full article →