How to write a systemd unit service file
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?
- The
Unitconfig docs https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html - The
Serviceconfig docs https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html
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
systemctlwill 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 asNo 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-reloadwhen you change your systemd unit service file, you need to reload systemdjournalctl -fu rqlite- look at /follow the logs for your servicesystemctl 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