Skip to main content

systemd services explained

Manage, debug and write systemd units — the thing that runs your long-lived processes.

systemd is the init system of most Linux distributions: it starts services at boot and keeps them running. Most "how do I run this in the background" questions have a systemd answer.

Managing services

bash
systemctl status myapp        # running? recent log lines?
systemctl start myapp
systemctl stop myapp
systemctl restart myapp
systemctl reload myapp        # re-read config without dropping connections
systemctl enable myapp        # start at boot
systemctl disable myapp       # do not start at boot
systemctl list-units --failed # what is broken?

Reload vs restart: reload re-reads configuration in place (no downtime) where the service supports it; restart stops and starts (brief downtime). Prefer reload for web servers.

Writing a unit

/etc/systemd/system/myapp.service:

ini
[Unit]
Description=My App
After=network.target postgresql.service

[Service]
Type=simple
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
EnvironmentFile=/etc/myapp/env
ExecStart=/usr/bin/node /opt/myapp/server.js
Restart=on-failure
RestartSec=5

# hardening
NoNewPrivileges=true
PrivateTmp=true

[Install]
WantedBy=multi-user.target

Then:

bash
systemctl daemon-reload      # after editing any unit file
systemctl enable --now myapp
systemctl status myapp --no-pager

The settings that matter most

  • User= / Group= — never run as root if you can avoid it.

  • EnvironmentFile= — keep secrets out of the unit file itself (which is world-readable).

  • Restart=on-failure — auto-recover crashes.

  • After= — start order dependencies (e.g. after the database).

  • Type= — simple for a foreground process; forking for one that daemonises.

Debugging a service that will not start

bash
systemctl status myapp --no-pager     # the first place to look
journalctl -u myapp -n 100 --no-pager # the actual error
journalctl -u myapp -f                # live

Common causes: wrong ExecStart path, missing permissions on the working directory, an environment file that does not exist, or a port already in use.

Timers (cron, the systemd way)

systemd can replace cron with timers. A .timer unit triggers a matching .service. Advantages: logging via the journal, and dependency ordering. For simple scheduled jobs, classic cron is still perfectly fine — use whichever you will remember.

Related reading