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.03.03 — Nightmare Spoofing. Evil Twin attack over dynamic routing

Attacks on dynamic routing domains can wreak havoc on the network since they disrupt the routing process. In this article, I am going to present my own…

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 →
2022.02.16 — Timeline of everything. Collecting system events with Plaso

As you are likely aware, forensic analysis tools quickly become obsolete, while hackers continuously invent new techniques enabling them to cover tracks! As…

Full article →
2022.02.15 — EVE-NG: Building a cyberpolygon for hacking experiments

Virtualization tools are required in many situations: testing of security utilities, personnel training in attack scenarios or network infrastructure protection, etc. Some admins reinvent the wheel by…

Full article →
2022.02.15 — First contact: How hackers steal money from bank cards

Network fraudsters and carders continuously invent new ways to steal money from cardholders and card accounts. This article discusses techniques used by criminals to bypass security…

Full article →
2023.07.20 — Evil modem. Establishing a foothold in the attacked system with a USB modem

If you have direct access to the target PC, you can create a permanent and continuous communication channel with it. All you need for this…

Full article →
2022.01.11 — Pentest in your own way. How to create a new testing methodology using OSCP and Hack The Box machines

Each aspiring pentester or information security enthusiast wants to advance at some point from reading exciting write-ups to practical tasks. How to do this in the best way…

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.01.22 — Top 5 Ways to Use a VPN for Enhanced Online Privacy and Security

This is an external third-party advertising publication. In this period when technology is at its highest level, the importance of privacy and security has grown like never…

Full article →
2023.02.13 — First Contact: Attacks on Google Pay, Samsung Pay, and Apple Pay

Electronic wallets, such as Google Pay, Samsung Pay, and Apple Pay, are considered the most advanced and secure payment tools. However, these systems are also…

Full article →