XK0-006 · Services and User Management · Updated July 26, 2026
systemd Unit Files: Service Types, Restart= Policies, and Safe Reloads
A systemd unit file is an INI-style text file that tells systemd how to manage a service: what to run, when it counts as “started,” what to do when it dies, and when to pull it in at boot. The three decisions that matter most in the [Service] section are Type= (how systemd knows startup finished), Restart= (what failures trigger an automatic restart), and ExecReload= (whether the service can re-read its config via systemctl reload without being killed). Getting these right is the difference between services that recover themselves and services that page you at 3 a.m.
Unit file structure and where files live
A service unit has three standard sections:
[Unit]
Description=Custom monitoring agent
After=network-online.target
Wants=network-online.target
[Service]
Type=notify
ExecStart=/usr/local/bin/monagent --config /etc/monagent.conf
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
[Unit] holds description and dependency ordering (After=, Requires=, Wants=); [Service] defines the process lifecycle; [Install] says what systemctl enable hooks the unit into — usually a systemd target such as multi-user.target. Vendor units live in /usr/lib/systemd/system/ (or /lib/systemd/system/ on Debian-family), administrator units and overrides in /etc/systemd/system/ — and /etc always wins over /usr/lib. Rather than copying a whole vendor file to change one line, use systemctl edit <unit>, which creates a drop-in at /etc/systemd/system/<unit>.d/override.conf containing only your overrides. After any on-disk unit change, run systemctl daemon-reload so systemd re-parses the files — edits are invisible until you do.
Type=: how systemd decides the service has started
Type= doesn’t change what your process does; it changes when systemd declares the unit active and lets dependent units proceed. Choosing wrong causes race conditions or hung startups.
| Type= | “Started” means… | Use for |
|---|---|---|
simple | The moment the process is forked — no readiness check at all | Foreground daemons where dependents don’t care about readiness |
exec | The execve() of the binary succeeded | Like simple, but catches a missing/unexecutable binary |
forking | The initial process forks a child and exits (classic daemonization) | Legacy SysV-style daemons; pair with PIDFile= |
oneshot | The process has run to completion and exited | Setup scripts, migrations, one-time jobs; often RemainAfterExit=yes |
notify | The daemon itself sends READY=1 over systemd’s notification socket | Daemons that must signal true readiness before dependents start |
idle | Like simple, but delayed until other jobs finish dispatching | Cosmetic; console-output tidiness |
Two of these carry most of the exam weight. Type=oneshot is for a script that performs a single task — a database migration, a firewall setup — where the unit should be considered successfully started only after the script finishes, not while it’s still running. With RemainAfterExit=yes, the unit then stays “active (exited)” so other units can order against its completion.
Type=notify fixes the classic Type=simple race: under simple, systemd marks the service started the instant it’s forked, so dependent units can launch before the daemon has, say, finished binding its listening socket. Switching to notify makes systemd wait — but it isn’t free: the daemon’s own code must explicitly send a readiness notification (READY=1) over the socket systemd passes in the NOTIFY_SOCKET environment variable, typically by calling sd_notify() from libsystemd (or shelling out to systemd-notify) once initialization is genuinely complete. A daemon that never sends READY=1 under Type=notify will sit in “activating” until it hits the start timeout and is killed. That code change is the whole point: readiness moves from systemd’s guess to the daemon’s own declaration.
Restart=: automatic recovery policy
Restart= in [Service] governs unattended recovery. Values worth knowing: no (default), on-success, on-failure, on-abnormal, on-watchdog, on-abort, and always.
The workhorse is Restart=on-failure: systemd restarts the service when it exits uncleanly — a non-zero exit code, death by unhandled signal (a segfault from that leaky third-party library), a timeout, or a watchdog trip — but not when it exits cleanly. Crucially, a deliberate systemctl stop never triggers a restart under any policy; systemd distinguishes operator intent from failure. That’s why on-failure is the right answer for “restart it when it crashes, but let me stop it on purpose”: Restart=always would also work for crashes but resurrects the service even after clean exits, and both policies still respect a manual stop. Tuning knobs: RestartSec= (delay before restarting, default 100 ms) and the rate-limiter pair StartLimitIntervalSec=/StartLimitBurst=, which stops a crash-looping unit from restarting forever.
reload vs restart: not dropping connections
systemctl restart kills the service and starts it fresh — every active connection dies. systemctl reload instead runs the unit’s ExecReload= command (commonly kill -HUP $MAINPID, or a native reload like nginx -s reload), asking the running process to re-read its configuration in place. If the application supports live config re-reads — and the scenario says it does — reload is the correct, least-disruptive choice, and it only exists when the unit defines ExecReload=. The hedge systemctl reload-or-restart reloads when possible and falls back to a restart otherwise.
Keep the two reload verbs straight, because the exam loves this confusion: systemctl reload <unit> tells the service to re-read its own config; systemctl daemon-reload tells systemd to re-read unit files. After editing a .service file you need daemon-reload; after editing the application’s config you want reload (or restart).
Verifying behavior afterward is a systemctl status plus journal check — journalctl commands covers reading a specific unit’s logs. And when the “service” is really a scheduled job, a timer unit may fit better than a service restarting in a loop — see systemd timers vs cron.
How the XK0-006 exam tests this
- A scenario where an app supports re-reading config without dropping live connections and the unit defines
ExecReload=— the discrimination issystemctl reloadover restart, and over daemon-reload (which targets unit files, not app config). - A crash-recovery scenario: a service dies intermittently from a bug but must not auto-start after an intentional stop — testing
Restart=on-failureagainstalwaysand against manual-stop behavior. - A
Type=selection pattern: a run-to-completion migration script (oneshot) versus a daemon whose dependents raced ahead of its socket setup (notify), sometimes asking what the daemon must do in code for notify to function — sendREADY=1viasd_notify. - An “edits not taking effect” pattern: an admin changes a unit file and restarts the service but sees old behavior — the missing step is
systemctl daemon-reload.
Unit files anchor the Services and User Management domain — the full XK0-006 study guide shows how heavily it is weighted, which makes XK0-006 practice questions on unit files some of the highest-value drilling you can do.
Quick reference
- Sections:
[Unit](description, ordering),[Service](lifecycle),[Install](enable target). Admin units in/etc/systemd/system/override vendor units in/usr/lib/systemd/system/. systemctl edit <unit>creates a drop-in override; full unit files don’t need copying.Type=oneshot: unit is started only after the process exits; addRemainAfterExit=yesto hold it active.Type=notify: systemd waits for the daemon to sendREADY=1(sd_notify/systemd-notifyviaNOTIFY_SOCKET) — requires daemon code support.Restart=on-failure: restart on unclean exits only;always: restart on any exit; no policy overrides a manualsystemctl stop.RestartSec=sets restart delay;StartLimitIntervalSec=/StartLimitBurst=prevent infinite crash loops.systemctl reload= app re-reads its config viaExecReload=, connections survive;restart= full stop/start;reload-or-restart= best available.systemctl daemon-reloadafter editing unit files — always.