systemd: run your app as a service and schedule jobs with timers

systemd keeps your app running, restarts it on failure and schedules jobs. Learn a minimal service file, timers and the commands you will use daily.

A service box with a restart arrow and a clock

systemd is the init system on most modern Linux distributions. It starts services at boot, restarts them when they crash, collects their logs and runs scheduled jobs. For a small server, it is the easiest way to run your own app reliably.

A minimal service

Create /etc/systemd/system/myapp.service:

[Unit]
Description=My web app
After=network-online.target

[Service]
User=deploy
WorkingDirectory=/srv/myapp
EnvironmentFile=/etc/myapp.env
ExecStart=/usr/bin/node /srv/myapp/server.js
Restart=on-failure
RestartSec=3

[Install]
WantedBy=multi-user.target

What the lines do:

  • After=: start after the network is up.
  • User=: run as a normal user, not root.
  • EnvironmentFile=: load settings and secrets from a file (keep it chmod 600) instead of putting them in the unit.
  • ExecStart=: the command to run, with the full path.
  • Restart=on-failure: bring it back if it crashes.
  • WantedBy=multi-user.target: start at normal boot when enabled.

The commands you will use

sudo systemctl daemon-reload        # after editing a unit file
sudo systemctl enable --now myapp   # start now and at every boot
sudo systemctl status myapp         # state and the last log lines
sudo systemctl restart myapp
sudo systemctl stop myapp
sudo systemctl disable myapp

Logs with journalctl

systemd collects what your app prints.

journalctl -u myapp                 # all logs for the service
journalctl -u myapp -f              # follow live
journalctl -u myapp --since "1 hour ago"
journalctl -u myapp -p err          # errors only

Hardening options (optional but useful)

Inside [Service] you can restrict what the app can do, for example:

  • NoNewPrivileges=true
  • PrivateTmp=true
  • ProtectSystem=full
  • ProtectHome=true

Add them one at a time and test, because a strict setting can stop a legitimate app from working.

Timers: a modern replacement for cron

A timer runs a service on a schedule. You need two files with the same name: a .service (what to run) and a .timer (when).

/etc/systemd/system/backup.service

[Unit]
Description=Daily backup

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh

/etc/systemd/system/backup.timer

[Unit]
Description=Run the backup every day

[Timer]
OnCalendar=*-*-* 03:30:00
Persistent=true

[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
systemctl list-timers

Handy options:

  • OnCalendar=: a calendar time (daily, weekly, or a specific time).
  • OnUnitActiveSec=15min: repeat 15 minutes after the last run.
  • Persistent=true: if the machine was off at the scheduled time, run it when it comes back.
  • RandomizedDelaySec=: spread load.

Compared with cron, timers give you logs in the journal, dependency handling, and easy status checks.

Common mistakes

  • Forgetting daemon-reload after editing.
  • Relative paths in ExecStart (use full paths).
  • A service that runs as root when it does not need to.
  • Putting secrets directly in the unit file, which many users can read.

Takeaway: a short unit file gives you auto-start, auto-restart and clean logs. Use timers for scheduled jobs, run services as a normal user, and keep secrets in a locked-down environment file.


Linux & DevOps series, Part 1 (servers and containers): 2 of 5. Previous: Linux file permissions explained: chmod, chown and umask.

Keep reading