skip to content

Units and Targets

The unit file as systemd's basic object — service, socket, mount, timer, path, device and slice types, and the [Unit]/[Service]/[Install] sections — plus targets as the dependency groupings that replaced SysV runlevels. Interviewers start here because you cannot debug systemd until you can read a unit file section by section.

on this pageshow

questions

6

Walk through a minimal systemd service unit file: which three sections does it have, and which of Description=, After=, ExecStart=, User= and WantedBy= belongs in each? What does systemd do with a key placed in the wrong section?

level: juniorimportance: must knowfreq 64%

answer

  1. three sections, three different readers
  2. one section is generic metadata
  3. one says how the process runs
  4. one is read only at enable time
  5. misplaced keys warn, they do not move

basics

~20 s

[Unit] holds Description= and relationship keys such as After=; [Service] holds how to run the process, including ExecStart= and User=; [Install] holds WantedBy= and is read only when the unit is enabled. A key in the wrong section is ignored with a warning.

solid answer

~40 s

A service unit has three sections. `[Unit]` is generic metadata and relationships: `Description=`, `Documentation=`, and the keys that tie this unit to others such as `After=`. `[Service]` is type-specific and says how the process runs: `ExecStart=`, `User=`, `WorkingDirectory=`, `Environment=`, `Type=`. `[Install]` is not read at runtime at all — `systemctl enable` and `disable` read it, and `WantedBy=` lives there. Section membership is enforced by the parser, not by convention: put `ExecStart=` under `[Unit]` and systemd logs that it does not recognise that key in that section, ignores it, and then refuses the unit because a service with no `ExecStart=` has nothing to run. Nothing is auto-corrected and nothing is inherited between sections. `systemd-analyze verify` on the file catches misplaced and misspelled keys before you ever try to start it.

code

ini · 10 lines
ini
[Unit]
Description=Example API server
After=network-online.target

[Service]
ExecStart=/usr/local/bin/apid --config /etc/apid.toml
User=apid

[Install]
WantedBy=multi-user.target

go deeper

for a junior

Be able to write a three-section service unit from memory and say which of Description=, ExecStart= and WantedBy= goes where. Know that ExecStart= needs an absolute path.

for a middle

Explain that [Install] is read only by enable and disable, that the parser drops unrecognised keys with a warning rather than failing loudly, and why ExecStart= is not a shell command line.

for a senior

Demonstrate the verification habit — systemd-analyze verify before deploying and systemctl show -p to prove a property resolved — and be ready to explain why a silently ignored key is a realistic production defect.

for a principal

Own the standard: a reviewed unit template for the organisation, verification wired into CI so misplaced or misspelled keys never reach a host, and a policy on which sandboxing keys are mandatory in [Service].

## The shape of a unit file A unit file is an INI-style text file: `[Section]` headers, `Key=Value` lines, `#` or `;` comments, and a trailing backslash to continue a long line. A minimal service looks like this: ```ini [Unit] Description=Example API server After=network-online.target [Service] ExecStart=/usr/local/bin/apid --config /etc/apid.toml User=apid [Install] WantedBy=multi-user.target ``` Three sections, three different jobs, three different readers. ## [Unit] — who this unit is and how it relates to others `[Unit]` is generic: every unit type may have one. It carries the human-facing metadata — `Description=`, which is the text `systemctl status` prints, and `Documentation=`, which holds man-page or URL references — and the keys that express relationships to other units, such as `After=` for ordering and `Wants=` for a dependency. `ConditionPathExists=` and its siblings also live here: they let a unit skip cleanly instead of failing when a precondition is absent. One rule catches people out: `Description=` is free text used only for display, so it does not need quoting, and putting a value there has no effect on anything systemd does. It is nonetheless worth writing, because on an unfamiliar host `systemctl status` output is the first thing anyone reads. ## [Service] — how the process runs `[Service]` is the type-specific section, valid only in a `.service` unit. It answers "what do I execute and under what conditions": - `ExecStart=` — the command line. It is not a shell command: systemd executes it directly, so the binary must be an absolute path and shell features like pipes, globs and variable expansion do not apply unless you deliberately invoke a shell. - `Type=` — how systemd decides startup is complete (`simple`, `exec`, `forking`, `oneshot`, `notify`, `dbus`, `idle`). - `User=`, `Group=` — the identity the process runs as. - `WorkingDirectory=`, `Environment=`, `EnvironmentFile=` — the process's environment. - The sandboxing and resource keys — `ProtectSystem=`, `PrivateTmp=`, `MemoryMax=` and so on. Because `ExecStart=` is not run through a shell, `ExecStart=/usr/bin/foo > /var/log/foo.log` does not redirect anything; the `>` is passed to `foo` as an argument. Output handling belongs to `StandardOutput=`/`StandardError=` instead. ## [Install] — read only by enable and disable `[Install]` is the section the running manager never consults. `systemctl enable` reads it to work out which symlinks to create, and `systemctl disable` reads it to work out which to remove. `WantedBy=` is the usual content. A unit with no `[Install]` section runs perfectly well when started by hand or pulled in by another unit; it simply cannot be enabled. ## What happens when a key is in the wrong section The parser knows which keys belong to which section, and it does not move them for you. Writing `ExecStart=` under `[Unit]` produces a log line to the effect that the key name is unknown in that section and is being ignored — and then the unit fails to load, because a service unit with no `ExecStart=` has nothing to run and systemd refuses it. A misspelling behaves identically: `ExecStrat=` is simply an unknown key, silently dropped apart from that warning. That failure mode — a warning in the journal rather than a hard parse error — is why misplaced keys survive review. Two habits catch them: ``` systemd-analyze verify /etc/systemd/system/apid.service systemctl show -p ExecStart -p User apid.service ``` `systemd-analyze verify` loads the unit the way the manager would and reports unknown keys and unresolvable dependencies without touching the running system. `systemctl show -p` prints the value systemd actually resolved for a property, which is how you prove a setting took effect rather than assuming it did. ## Section names are case-sensitive `[service]` is not `[Service]`, and `execstart=` is not `ExecStart=`. Section and key names are matched case-sensitively; values generally are not (booleans accept `yes`/`true`/`1`). This trips people who copy a unit out of a blog post that lost its capitalisation.

  • Why does ExecStart=/usr/bin/foo > /var/log/foo.log not redirect the output?
    `ExecStart=` is not a shell command line. systemd splits it into an argument vector and executes the binary directly, so `>` is handed to `foo` as a literal argument and no redirection happens. Output is controlled with `StandardOutput=` and `StandardError=` instead, or you invoke a shell explicitly with `ExecStart=/bin/sh -c '...'` and accept the extra process.
  • A unit file contains ExecStrat= instead of ExecStart=. What does systemd report?
    The parser does not recognise the key in that section, logs a warning that it is being ignored, and continues. The unit then fails to load because a service with no `ExecStart=` has nothing to run. There is no hard syntax error, so the typo is easy to miss — `systemd-analyze verify` surfaces it, and `systemctl show -p ExecStart` shows the property empty.
  • Which of the three sections may a unit file legitimately omit?
    `[Install]` — it is only consulted by enable and disable, so a unit without it starts normally by hand or when another unit pulls it in; `systemctl list-unit-files` shows such units as static. `[Unit]` is technically optional too, though omitting it loses the description and any relationships. A service without `[Service]` and its `ExecStart=` cannot run at all.

saying these in an interview costs you the question

  • systemd moves a misplaced key to the right section automatically
  • ExecStart= runs through a shell, so pipes and redirection work
  • [Install] tells systemd how to start the service
  • Section headers are decorative, only the keys matter
  • A typo in a key name causes a hard parse error you cannot miss

context

open as a page

On a systemd host you edited /usr/lib/systemd/system/nginx.service directly, and a package upgrade silently reverted your change. Which directories does systemd load unit files from, in what order of precedence, and how should that change have been made so it survives upgrades?

level: middleimportance: must knowfreq 68%

basics

~20 s

systemd loads units from /etc/systemd/system (admin, wins), /run/systemd/system (runtime), and /usr/lib/systemd/system (package-owned, loses). Package upgrades rewrite the last one, so put changes in a drop-in created with systemctl edit instead of editing the vendor file.

open as a page

systemd unit files carry suffixes such as .service, .socket, .timer, .mount, .path, .slice and .scope. What does that suffix determine, and what kind of object does each of those unit types manage?

level: juniorimportance: should knowfreq 58%

basics

~20 s

The suffix is the unit type: .service supervises a process, .socket owns a listening socket, .timer carries a schedule, .mount a mount point, .path watches files, .slice groups processes for resource control, and .scope adopts processes started outside systemd.

open as a page

On a systemd host, `systemctl enable myapp.service` refuses with a message that the unit file has no installation config. Which section is missing from the unit file, and what exactly does WantedBy=multi-user.target cause systemd to create on disk?

level: middleimportance: should knowfreq 52%

basics

~10 s

The unit has no [Install] section, so there is nothing telling systemctl where to hook it. WantedBy=multi-user.target makes enable create a symlink to the unit inside /etc/systemd/system/multi-user.target.wants/, which is all that enabling physically does.

open as a page

A configuration-management run adds the drop-in /etc/systemd/system/app.service.d/override.conf containing an [Service] section with a new ExecStart= line, and after daemon-reload the unit refuses to start, complaining it has more than one ExecStart=. Why does that happen, and how do you correctly replace a directive from a drop-in?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Drop-ins merge rather than replace, and list-valued settings such as ExecStart= append, so the unit ended up with two. Reset the list first by assigning the key an empty value, then set the new one — two lines: ExecStart= followed by ExecStart=/new/command.

open as a page

In systemd, what is a template unit such as [email protected], how do you start and enable one instance of it, and what do the %i and %I specifiers expand to inside the unit file?

level: middleimportance: nice to knowfreq 30%

basics

~20 s

A template unit has an @ before its suffix and is never run directly; you run instances of it, such as [email protected]. Inside the file, %i expands to the instance name as written and %I to its unescaped form.

open as a page