How to write a systemd unit service file

How to write a systemd unit service file
Photo by ダモ リ on Unsplash

Remind me how to write a systemd serive and the required steps/Unit file.

Say you want to run rqlite (any program you want to run as a service) as a systemd service.

Step one - always check if the maintainer(s) of your program already ship / document a systemd confguration. They probably know better than you what reasonable defaults to use. Or a simple Google rqlite systemd brings back a good starter:

File path: /etc/systemd/system/rqlite.service

[Unit]
Description=rqlite
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/usr/bin/rqlited -http-addr 0.0.0.0:4001 -raft-addr 0.0.0.0:4002 /path/to/datadir
User=youruser
ExecReload=/bin/kill -HUP $MAINPID
KillSignal=SIGTERM
Restart=always

[Install]
WantedBy=multi-user.target

But what do those options mean?

Where do I put the systemd service file?

The systemd docs state:

When packages need to customize the configuration, they can install drop-ins under /usr/. Files in /etc/ are reserved for the local administrator, who may use this logic to override the configuration files installed by vendor packages. Drop-ins have to be used to override package drop-ins, since the main configuration file has lower precedence. It is recommended to prefix all filenames in those subdirectories with a two-digit number and a dash, to simplify the ordering. This also defines a concept of drop-in priorities to allow OS vendors to ship drop-ins within a specific range lower than the range used by users. This should lower the risk of package drop-ins overriding accidentally drop-ins defined by users. It is recommended to use the range 10-40 for drop-ins in /usr/ and the range 60-90 for drop-ins in /etc/ and /run/, to make sure that local and transient drop-ins take priority over drop-ins shipped by the OS vendor.

To disable a configuration file supplied by the vendor, the recommended way is to place a symlink to /dev/null in the configuration directory in /etc/, with the same filename as the vendor configuration file.

Common pitfalls / mistakes

  • You don't know how to see the logs for a failing systemd service. Answer: journalctl -fu <service-name> will tail/follow the logs of your service
  • Don't know the same of the service? Running systemctl will list all units, then you can search them with / and type..
  • ExecStart , if you pass a script- you forgot to update your paths to use absolute paths rather than relative paths and see erros such as No such file or directory

How do I make my systemd service start at boot time?

You have to enable it. e.g.:

systemctl enable rqlite
Created symlink /etc/systemd/system/multi-user.target.wants/rqlite.service → /etc/systemd/system/rqlite.service.

How do I know/ check my systemd unit serice is configured to start at boot time?


After running systemctl status rqlite you'll see "`enabled`" on your unit output

● rqlite.service - rqlite
     Loaded: loaded (/etc/systemd/system/rqlite.service; enabled; preset: enabled)
    Drop-In: /run/systemd/system/service.d
             └─zzz-lxc-service.conf
     Active: active (running) since Tue 2025-10-28 11:38:54 UTC; 1min 41s ago

How do I stop a systemd service starting at boot time?

Disable it, e.g.:

systemctl disable rqlite
Removed "/etc/systemd/system/multi-user.target.wants/rqlite.service".

Typical commands to activate/load/restart your new systemd service

  • systemctl daemon-reload when you change your systemd unit service file, you need to reload systemd
  • journalctl -fu rqlite - look at /follow the logs for your service
  • systemctl restart rqlite.service

How do I get the current directory of my ExecStart script so I don't have to hardcode it?

Say you have a shell script to start your systemd service start.sh and you don't want to hardcode paths to the various config files, you can use a combinsation of $0, dirname and readlpath to determine the path at runtime.

For example: Say you have: ExecStart=/path/start.sh

#!/bin/bash
set -x
# i/we wrote this not rqlite

SCRIPT_PATH=$(dirname $(realpath $0))

$SCRIPT_PATH/rqlited -auth $SCRIPT_PATH/config.json -node-id=$(cat /etc/machine-id) /path/to/data

Collect recurring payments with Subscribie