On this page
Defining a service
A service is defined by a single key in the registry, under Machine\System\Services\<name>. The key's name is the service name, and the typed values inside it are the definition — the binary, who it runs as, what it depends on, how it is supervised. There is no file under /etc; there is a registry key.
peinit reads these definitions in two situations: once at boot, to build the service graph, and on demand, when an administrator starts a service or runs reload-config. Between those reads it works from an in-memory copy. That last fact is the source of the most common "why didn't my change take effect?" question, and the second half of this page is devoted to it.
Service names
A service name must be made of characters from [A-Za-z0-9._-] and be 1–128 bytes long. Anything else is a validation error. Two characters are pointedly excluded:
/— names map directly onto cgroup ids and registry key names, and a slash would be ambiguous in both.:— reserved for peinit-internal synthetic names (for example, the way a hook job is labelled).
The name is how you refer to the service everywhere: in peiosctl commands, in another service's dependency list, in a ServiceSecurity descriptor, and in the per-service SID derived from it.
The definition schema
A definition is a set of typed registry values. Rather than list all of them in one wall of rows, the tables below group the fields by what they are for, with a pointer to the page that explains each group in depth. Every field is optional unless noted; the only hard requirement is ImagePath. The full type-and-default catalogue lives in the Registry key reference.
What to run — the binary and its execution context.
| Field | Default | Purpose |
|---|---|---|
ImagePath (required) |
— | Absolute path to the service binary. |
Arguments |
— | Argument list passed to the binary. |
WorkingDirectory |
/ |
Working directory for the process. |
Environment |
— | KEY=VALUE pairs added to the environment. |
RuntimeDirectories |
— | Private directories created under /run just before the process starts. See The execution environment. |
LimitNOFILE, LimitCORE |
— | RLIMIT_NOFILE / RLIMIT_CORE. |
What kind of service — type and readiness. See Simple and Oneshot services.
| Field | Default | Purpose |
|---|---|---|
Type |
Simple | Simple (long-running) or Oneshot (run-to-completion). |
Readiness |
Notify | Notify (READY=1 via sd_notify) or Alive (ready when the process exists). Ignored for Oneshot. |
RemainAfterExit |
0 | Oneshot only — stay in Completed after a successful exit. |
SuccessExitCodes |
— | Non-zero exit codes to treat as success. |
When to start — triggers and conditions. See Triggers and timers.
| Field | Default | Purpose |
|---|---|---|
Triggers |
— | boot and/or timer:<schedule>. Absent = demand-only. |
Disabled |
0 | If 1, triggers must not activate the service (manual start still allowed). |
SafeMode |
0 | If 1, attempt to start in Safe mode. |
Conditions, Asserts |
— | Start-time checks. A failed condition skips; a failed assert fails. |
Who it runs as — identity and privileges. See Service identity and privileges.
| Field | Default | Purpose |
|---|---|---|
Identity |
LocalService |
Principal name or SID for the service token. |
RequiredPrivileges |
— | Privilege allow-list; everything else is stripped from the token. |
HookIdentity |
service's Identity |
Identity for ExecStartPre/ExecStartPost hooks. |
How it relates to other services — dependencies. See Dependencies and ordering.
| Field | Purpose |
|---|---|
Requires |
Hard dependencies — must be satisfied first; their failure fails this service. |
Wants |
Soft dependencies — started first if present, but optional. |
BindsTo |
Runtime coupling — if the target stops, this stops too. |
Conflicts |
Mutual exclusion — starting this stops the named services. |
OnFailure |
Service to start when this one enters Failed. |
How it is kept alive — supervision and health. See Keeping services running.
| Field | Default | Purpose |
|---|---|---|
ErrorControl |
Normal | Normal (stay Failed) or Critical (sync + reboot on irrecoverable failure). |
RestartPolicy |
OnFailure | Never / OnFailure / Always. |
RestartMaxRetries, RestartWindow, RestartDelay |
5 / 120 / 1 | Restart budget, the window of health that resets it, and the backoff base. |
HealthCheck, HealthCheckInterval, HealthCheckTimeout, HealthCheckRetries |
— / 30 / 5 / 3 | Active health-check command and its timing. |
WatchdogTimeout |
0 | Expected interval between WATCHDOG=1 pings; 0 disables. |
The transition phases — hooks and timeouts. See The execution environment and The service lifecycle.
| Field | Default | Purpose |
|---|---|---|
ExecStartPre, ExecStartPost |
— | Commands run before the binary / after readiness. |
ExecReload |
(SIGHUP) | Reload command or signal:<NAME>. |
StartTimeout, StopTimeout |
30 / 10 | Seconds for the whole start sequence / between SIGTERM and SIGKILL. |
The remaining knobs — scheduling, notify, fds, metadata.
| Field | Default | Purpose |
|---|---|---|
TimerPersistent, TimerJitter |
1 / 0 | Catch up missed timer runs after reboot / random delay per firing. |
NotifyAccess |
Main | Who may send sd_notify messages (only Main is supported). |
FdStoreMax |
0 | Size of the per-service fd store; 0 disables it. |
ServiceSecurity |
inherit | Security descriptor controlling who may manage the service. |
DisplayName, Description |
— | Human-readable labels for status output. |
Forward compatibility
The schema version lives at Machine\System\Services\SchemaVersion (currently 1). peinit is deliberately forward-compatible:
- Unknown values are ignored. A definition written for a newer peinit does not break an older one.
- A newer schema version does not block boot. peinit logs a warning and continues.
- The schema only grows. New capability arrives as new optional fields, never as a breaking change to an existing one.
One defensive rule cuts the other way: a known field must not appear more than once in a collected definition. A duplicated known field is a validation error, even though the registry would ordinarily give you at most one value per name.
peinit works from a snapshot, not the live registry
Here is the idea that explains most surprises. peinit does not re-read the registry every time it touches a service. It reads definitions at well-defined moments and operates on an in-memory model in between.
Two layers of snapshotting stack on top of each other:
- Boot generation. At the start of boot, peinit reads all definitions, builds the dependency graph, and validates it. The whole boot runs against that one snapshot. Changes made during boot — by an install script, a post-hook — do not perturb the boot in progress.
- Activation generation. When peinit starts a specific service, it snapshots that service's definition for the entire start. Pre-exec hooks, the token request, the readiness timeout, the first health checks — all use the values captured at activation. Edit a field while the service is
Starting, and the edit waits for the next start.
peinit learns about registry edits through change notifications: it subscribes to Machine\System\Services\ and Machine\System\Init\ at boot, and processes events in its event loop at a time of its choosing. If the notification queue overflows — a bulk admin operation, a flurry of scripted writes — peinit detects the overflow marker and does a full reload-config to resynchronise. You never have to think about the queue; you do have to know that a change is picked up, not pushed, and that when it takes effect depends on the field.
Field mutability: when a change takes effect
Every field falls into one of four mutability classes. This table is the one to keep handy.
| Class | When a change takes effect | Fields |
|---|---|---|
| Immutable at runtime | Next restart only. | ImagePath, Type, Identity, RequiredPrivileges, ErrorControl |
| Apply on next start | Next start or explicit graph reload — not while running. | Requires, Wants, BindsTo, Conflicts, OnFailure, Conditions, Asserts |
| Hot-reloaded | Next relevant event, no restart. | ServiceSecurity (next control request) |
| Reloadable at runtime | Next relevant operation (restart, health-check cycle, …), no restart. | Arguments, SuccessExitCodes, all timeout/retry values, the health-check fields, RestartPolicy, Environment, WorkingDirectory, the hooks, Readiness, NotifyAccess, LimitNOFILE/LimitCORE, FdStoreMax, SafeMode, DisplayName, Description |
The practical reading: changing what a process is or runs as (ImagePath, Identity, Type, privileges, ErrorControl) is fundamental enough that it only applies when a fresh process starts — you must restart. Changing policy that peinit consults each time it acts (timeouts, restart behaviour, health checks) is picked up the next time peinit acts. And ServiceSecurity is special: it is re-read on every control request, so an access-control change takes effect on the very next command without touching the running service.
For inactive services the rules are simpler, because there is no running process to protect: a new service entry becomes available once the change notification is processed; a changed timer arms on the next evaluation; a changed dependency takes effect on the next start.
reload-config is the explicit, atomic way to make peinit re-read everything. It builds and validates a complete new graph snapshot and swaps it in only if validation passes; running services are not disturbed. It is covered with the rest of the command set in Controlling services.Removing a service definition
Deleting a definition from the registry does not kill a running instance. peinit learns of the removal through the same notification path and behaves according to whether the service is running:
- Not running (Inactive, Failed, Completed, Skipped, Abandoned): peinit discards the in-memory entry immediately. There is nothing to supervise.
- Running (Active, Starting, Reloading, Backoff, Stopping): the running process is a job, and a job outlives its definition. peinit marks the entry definition-removed, keeps the cached definition only to finish supervising the existing instance, and does not restart it when it exits. Once it exits, the entry — and any stored fds — are discarded.
While an entry is definition-removed it keeps satisfying its dependents (it is still running), stop still works so you can drain it cleanly, but start, restart, and reload are rejected with UNKNOWN_SERVICE — there is no definition to start from. A status query reports it with a definition_removed: true flag so the draining instance is never invisible.
stop it. To prevent it from auto-starting, set Disabled=1 (see Triggers and timers). To prevent it from being started at all, deny SERVICE_START in its ServiceSecurity descriptor.Where to start
To understand the Type and Readiness fields and the Simple/Oneshot split, read Simple and Oneshot services.
To understand how a definition becomes a running, supervised process — and what each state in a status output means — read The service lifecycle.
For the complete type-and-default catalogue of every registry key peinit reads, see the Registry key reference.