Skip to content

systemd Services

We use systemd services to run a variety of binaries, scripts etc. These are configured by /etc/systemd/system/*.service files. The structure of these depends on the service in question but there are a number of commonalities and best practices you should keep in mind. We list these here by file section. The various options are documented in the systemd man pages.

  • [Unit]: At minimum you're going to want to add a Description. Optionally, use After to declare ordering dependencies and Wants/Requires for soft/hard dependencies. A very common pattern is e.g. the following for services that require a network connection:

    Description=My program
    After=network-online.target
    Wants=network-online.target
    
  • [Service]:

  • Add a Type, usually exec which is preferable over simple on systemd versions that support it for better startup error reporting.
  • Add User/Group for services that don't absolutely need to be run as root.
  • Specify the command to run (absolute path) via ExecStart. Binaries should usually be located under /usr/local/bin.
  • If your service can be reloaded via a mechanism such as SIGHUP, specify this via ExecReload, very commonly: ExecReload=/bin/kill -HUP $MAINPID.
  • You are almost always going to want to add Restart=on-failure (to keep the service up but still allow for intentional stopping) and a RestartSec if the 100ms default could cause crashloops that are too tight. A reasonable number is 5 seconds unless you need the service to come back up asap after a crash.
  • If your program needs environment files to be set, do this via EnvironmentFile instead of putting them into the service file itself. This should usually be /etc/default/my-program.
  • You should likely set WorkingDirectory just in case, if your program reads/writes files via relative paths things will break otherwise. This should usually be /var/lib/my-program.
  • Add basic security hardening flags, protecting the file system with ProtectSystem=strict (whitelisting write-paths with ReadWritePaths) ProtectHome=true, PrivateTmp=true and restricting the service's access to the system via NoNewPrivileges=true, RestrictSUIDSGID=true, ProtectKernelTunables=true and ProtectControlGroups=true.

  • [Install]: Unless you have some very specific reason not to, just use WantedBy=multi-user.target.

Here is a full example:

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

[Service]
Type=exec
User=my-program-user
Group=my-program-user
ExecStart=/usr/local/bin/my-program \
  -some-flag \
  -some-other-flag
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5
EnvironmentFile=/etc/default/my-program
WorkingDirectory=/var/lib/my-program
ProtectSystem=strict
ReadWritePaths=/var/lib/my-program
ProtectHome=true
PrivateTmp=true
NoNewPrivileges=true
RestrictSUIDSGID=true
ProtectKernelTunables=true
ProtectControlGroups=true

[Install]
WantedBy=multi-user.target