Expand values with systemd specifiers
Specifiers let a single systemd unit adapt to its context. Instead of hard-coding
a hostname, user or instance name, you write a %-token that systemd resolves
when it loads the unit. This is what makes template units ([email protected])
powerful: %i becomes whatever follows the @. This tool lists every specifier
with the value it expands to and a concrete example.
How it works
A specifier is a percent sign followed by a single character. When systemd parses a unit file it substitutes each known specifier with a runtime value before the option takes effect. The values come from several sources:
- Unit name specifiers:
%nfull unit name,%pprefix (everything before @),%iinstance (everything after @, unescaped),%jfinal component. - Escaped variants:
%P,%I,%J— same as above but with systemd’s path escaping preserved. - Host specifiers:
%Hhostname,%mmachine ID,%bboot ID,%vkernel release. - User specifiers:
%u/%Uusername/UID,%g/%Ggroup/GID,%hhome directory,%slogin shell. - Directory specifiers:
%truntime directory,%Sstate directory,%Ccache directory,%Llog directory,%Econfiguration directory. - Escape:
%%produces a literal%.
Because the escaped forms (%I, %P, %J) preserve systemd’s path encoding, they round-trip safely when the instance part of a unit name encodes a filesystem path (for example [email protected] where the - characters represent /).
Template units in practice
The most productive use of specifiers is in @ template units, where one unit file manages many instances. For example, a hypothetical [email protected] that backs up different directories might look like:
[Unit]
Description=Backup job for %i
[Service]
ExecStart=/usr/local/bin/backup --target=/var/backups/%I
WorkingDirectory=/srv/%i
User=%u
Here %i provides the human-readable instance name in Description= and the working directory path. %I provides the path-safe encoded form for the backup target, which matters if the instance name itself contains slashes encoded as dashes.
Enable instances with systemctl enable [email protected], systemctl enable [email protected], and so on — all from the single template file.
Reference: the most-used specifiers
| Specifier | Expands to | Typical use |
|---|---|---|
%i | Instance name (unescaped) | Description=, human-readable paths |
%I | Instance name (escaped) | Paths where instance may encode slashes |
%p | Unit prefix | Shared paths across instances of one template |
%H | Hostname | Per-host log filenames, environment variables |
%u | Username | User= default, home-relative paths |
%h | User home directory | WorkingDirectory=, config paths |
%t | Runtime directory (/run/user/UID) | Socket files, PID files |
%S | State directory | Persistent data that survives reboots |
Debugging tips
Verify what a unit will actually use with:
systemctl cat myservice@instance # shows resolved option values
systemd-analyze verify [email protected] # validates syntax
Both tools substitute specifiers and show the resolved values, so you can catch a mis-expanded %i before it causes a runtime surprise. When a specifier resolves to empty — which can happen for %u in a system-scope unit with no User= set — the option may silently collapse to an unexpected path.
A lone % followed by an unknown letter is left unexpanded by systemd but generates a warning; always use %% for a literal percent in option values.