Systemd is an init system and service manager used by many major Linux distributions. It aims to replace traditional Unix system startup scripts and bring process control functionality to the Linux platform. A very important part of using systemd is understanding how to write service unit configuration files, commonly referred to as just “services”. These files define how systemd should start, stop, reload, restart, and control processes and applications as system services.
Writing good service files is an essential Linux system administration task. It allows administrators to easily manage applications and daemons using the systemctl command and integrates them into the overall system initialization and runtime process structure. This article will provide an in-depth look at writing systemd service files, including the basic structure and directives used, how to configure keys settings like restart policies, environment variables, dependencies, socket activation, and more.
The Basic Structure
All systemd service unit files use the .service file extension and reside in the /etc/systemd/system/ directory by default. The basic structure and required settings are:
[Unit]
Description=Description of the service
[Service]
Type=service type (simple, forking, oneshot etc)
ExecStart=command to start service
ExecStop=command to stop service
Comment lines beginning with # can provide additional description or metadata. The [Unit] section contains settings relevant to the service unit as a whole, like aliases or dependencies. [Service] focuses on settings specific to starting, stopping and controlling the running service process.
At minimum, a valid service needs the [Unit] and [Service] sections, along with ExecStart and some short description. Most services will need additional configurations covered later in this article. Systemd provides defaults for some common settings if they are omitted.
Let’s take a look at a basic example smtp service unit file:
smtp service
[Unit]
Description=SMTP Service
After=network.target
[Service]
Type=forking
ExecStart=/usr/sbin/smtpd -n -t
ExecStop=/bin/kill -TERM $MAINPID
Restart Policies
Configuring how systemd should behave when a service crashes or fails is an important part of most services. The Restart= directive controls this and accepts values like:
no – Do not automatically restart service if it fails
on-failure – Restart only if service exits with non-zero code
always – Always restart regardless of exit code
For example:
[Service]
Restart=on-failure
RestartSec=5
This instructs systemd to automatically restart the service if it crashes or exits with a non-zero code. RestartSec= sets the time in seconds to wait between restart attempts.
Many network and database services want to configure Restart=always to ensure uptime in case of crashes or accidental termination. Services that run scripts or one-off tasks typically do not want restarts.
Environment Variables
Often services need to read configuration values or credentials from the environment. Systemd makes this easy using the Environment= directive:
[Service]
Environment=”DB_HOST=localhost”
Environment=”DB_USER=admin”
Environment=”DB_PASS=secret”
Any environment variables set here will be passed to processes started by the service. Systemd handles securely propagating these values similarly to systemd-run.
RuntimeDirectory=
Services may need to write runtime state, socket, pid or log files. Systemd cleans and resets service runtime directories between restarts by default. The RuntimeDirectory= directive avoids this and provides a stable location for such files:
[Service]
RuntimeDirectory=ntp
RuntimeDirectoryMode=0700
This tells systemd to place runtime files and sockets in /run/systemd/ntp instead of the private /run/systemd/service-$NAME directory.
Type=
The Type= directive classifies services and tells systemd the expected process behaviour. Common values include:
simple – Single process, exited on stop
forking – Fork on startup, parent exits immediately
oneshot – Single process, exited on completion
notify – Signal parent on startup completion
dbus – Register on system dbus on startup
Setting the correct Type= is important for proper systemd behavior and allows it to monitor services correctly.
User= and Group=
Many services need to run as a particular non-root user for security reasons. systemd supports setting the user and group contexts services should run under:
[Service]
User=nginx
Group=www-data
This ensures the nginx processes are started and controlled by systemd while running with reduced permissions isolated from the root user context.
WorkingDirectory=
Similar to setting a process working directory, this directive configures where files executed by a service’s ExecStart= will operate from:
[Service]
WorkingDirectory=/var/www/html
This is useful for setting the expected current working directory for a service’s main processes.
UMask=
The file mode creation mask or umask determines default file permissions for files/sockets created by a service. Systemd lets you configure this:
[Service]
UMask=0027
This would make newly created files have 0660 permissions instead of the default 0666.
PIDFile=
Some services need or expect a PID file to store their main process ID. Systemd supports configuring and monitoring these:
[Service]
PIDFile=/run/apache2/apache2.pid
It will check the PID stored inside on startup/restart and send signals to that PID on shutdown.
ExecReload=
Similar to ExecStart= but defines the command to reload service configuration files without fully restarting the service process.
[Service]
ExecReload=/etc/init.d/myapp reload
For services that support hot-reloading of configs, this provides a cleaner reload integration with systemctl.
StandardInput=, StandardOutput=, StandardError=
Advanced settings to redirect a service process standard input, output and error streams. Common values include:
journal – Log to journal
inherit – Inherit daemon defaultredirections
null – Discard output
kmsg – Redirect to kernel log
For example:
[Service]
StandardOutput=journal
StandardError=kmsg
This provides flexible logging options beyond just the default journald daemon logging.
SuccessExecExitStatus=
For services with non-standard exit codes, this allows configuring which exit codes systemd should consider as clean service stop/restart.
[Service]
SuccessExitStatus=0 1 142
So exits 0, 1, 142 are treated as clean, others as failure.
killMode=
Configures how systemd terminates services on shutdown. Common values are:
process – kill only main process
control-group – kill all processes in cgroup
For servers with forks/workers pick control-group to fully terminate all child processes.
Security… Settings
systemd provides security context and sandboxing features too via settings like:
SMFramework= – AppArmor or SELinux context
ProtectSystem= – Sandbox from sensitive parts of system
ProtectHome=true – Avoid accessing user home dirs
PrivateTmp=true – Isolate temp files
These help harden and secure the running service processes from potential exploits.
Dependencies and After/Before/Wants=
Systemd services have a rich dependency system to control startup/shutdown order between units. Common directives are:
Requires= – Fails to start if dependencies not met
After= – Start after these units activated
Before= – Start before these units
Wants= – Start/stop ordering but not fatal errors
So a database service unit could have:
[Unit]
After=network-online.target postgresql.service
Requires=postgresql.service
This ensures networking is up and PostgreSQL is running before starting the application depending on it. Dependencies make services modular and avoid start racing conditions.
Socket Activation
Systemd supports socket-based activation, which is especially useful for network services. It allows launching a service dynamically only when a client connects rather than keeping all processes constantly running.
The socket unit file listens on a port/socket and activates the service unit in response. A simple example:
myapp-socket.socket
[Unit]
Description=myapp socket
[Socket]
ListenStream=/run/myapp.sock
myapp.service
[Unit]
PartOf=myapp-socket.sock
[Service]
ExecStart=/usr/bin/myapp –socket /run/myapp.sock
Now whenever a connection appears on the socket, myapp is started on demand without needing a static process. This improves efficiency and resource usage for applications that often sit idle waiting for work.
In Summary
This covers many of the major settings and features available when writing systemd service files. Understanding the various directives gives administrators powerful tools to cleanly integrate applications and monitor services as first-class entities within the systemd service management framework. Well configured services improve stability, security and maintainability of any Linux system using the init system.
