Build systemd units with the right options
A systemd unit file is an INI-style description of a service, socket, timer or
other resource. Each option lives in a section and has a specific value type and
default. This tool is a searchable reference to the most-used options across the
[Unit], [Install], [Service], [Socket] and [Timer] sections, so you can
confirm exactly what to write before reloading the daemon.
Unit file anatomy
A unit file has a predictable structure: shared sections at the top, type-specific section below:
[Unit]
Description=My web service
After=network-online.target
Wants=network-online.target
[Service]
Type=notify
ExecStart=/usr/bin/myapp --config /etc/myapp.conf
Restart=on-failure
RestartSec=5s
User=myapp
[Install]
WantedBy=multi-user.target
[Unit] carries the human-readable description and the dependency/ordering directives. Requires is a hard dependency — if it fails, this unit stops too. Wants is soft — it tries to start the dependency but continues if it cannot. After controls ordering, not dependency: the unit starts after the listed units, but After alone does not pull them in. To both require and order, combine Requires=foo.service with After=foo.service.
[Install] controls what systemctl enable and systemctl disable do. WantedBy=multi-user.target creates a symlink in multi-user.target.wants/ so the unit starts at normal boot. Without an [Install] section, the unit cannot be enabled — it can only be started manually.
[Service] is where the behavior is defined: what binary to run (ExecStart), how systemd knows when it is ready (Type), when and how to restart it (Restart, RestartSec), and which user to run as (User, Group).
Service types explained
The Type= directive determines when systemd considers the service started:
| Type | When systemd marks it ready | Use case |
|---|---|---|
simple | Immediately after fork | Short commands, background daemons that are ready right away |
notify | After sd_notify(READY=1) | Well-behaved daemons that signal readiness (nginx, systemd-aware apps) |
forking | After the parent process exits | Old-style daemons that double-fork to background themselves |
oneshot | After ExecStart exits | Setup scripts, tasks that run once then stop |
idle | After boot-up jobs finish | Low-priority tasks that should not delay normal startup |
Use Type=notify for any service that supports it — dependents will only start once the service is genuinely ready to accept work.
Restart policies
Restart=on-failure restarts the service when the process exits with a non-zero code, on a signal, or on a timeout — but not when you explicitly stop it with systemctl stop. This is the right default for most services: it recovers from crashes without creating a restart loop when you deliberately stop the unit.
Restart=always restarts on any exit, including clean exits and systemctl stop. Use it only for processes that should genuinely never stop.
RestartSec=5s adds a delay between restarts. Without it, rapid crash loops hit the rate limit and systemd stops restarting after a few attempts.
Timer units
A timer unit triggers another service (or itself) on a schedule. The companion service should have the same base name:
# myapp-backup.timer
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.target
OnCalendar=daily fires once per day. Persistent=true ensures the timer fires immediately at next boot if the machine was off when the scheduled time passed — important for backup jobs and cleanup scripts. Check timer status with systemctl list-timers.
Common mistakes
- Forgetting
After=network-online.target(instead ofnetwork.target) for services that require actual connectivity.network.targetonly means the networking stack is configured;network-online.targetwaits for interfaces to be up and connected. - Using
Restart=alwayson a service that may legitimately exit with code 0 — causing a restart loop. - Missing the
[Install]section: the unit file is valid, butsystemctl enablesilently does nothing. - Editing a unit file without running
systemctl daemon-reloadafterward — the old definition stays cached until you reload.