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?
answer
- three sections, three different readers
- one section is generic metadata
- one says how the process runs
- one is read only at enable time
- 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 sA 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[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.targetgo deeper
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.
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.
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.
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