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 catalog lives in the Registry key reference.

What to run — the binary and its execution context.

FieldDefaultPurpose
ImagePath (required)Absolute path to the service binary.
ArgumentsArgument list passed to the binary.
WorkingDirectory/Working directory for the process.
EnvironmentKEY=VALUE pairs added to the environment.
RuntimeDirectoriesPrivate directories created under /run just before the process starts. See The execution environment.
LimitNOFILE, LimitCORERLIMIT_NOFILE / RLIMIT_CORE.

What kind of service — type and readiness. See Simple and Oneshot services.

FieldDefaultPurpose
TypeSimpleSimple (long-running) or Oneshot (run-to-completion).
ReadinessNotifyNotify (READY=1 via sd_notify) or Alive (ready when the process exists). Ignored for Oneshot.
RemainAfterExit0Oneshot only — stay in Completed after a successful exit.
SuccessExitCodesNon-zero exit codes to treat as success.

When to start — triggers and conditions. See Triggers and timers.

FieldDefaultPurpose
Triggersboot and/or timer:<schedule>. Absent = demand-only.
Disabled0If 1, triggers must not activate the service (manual start still allowed).
SafeMode0If 1, attempt to start in Safe mode.
Conditions, AssertsStart-time checks. A failed condition skips; a failed assert fails.

Who it runs as — identity and privileges. See Service identity and privileges.

FieldDefaultPurpose
IdentityLocalServicePrincipal name or SID for the service token.
RequiredPrivilegesPrivilege allow-list; everything else is stripped from the token.
HookIdentityservice's IdentityIdentity for ExecStartPre/ExecStartPost hooks.

How it relates to other services — dependencies. See Dependencies and ordering.

FieldPurpose
RequiresHard dependencies — must be satisfied first; their failure fails this service.
WantsSoft dependencies — started first if present, but optional.
BindsToRuntime coupling — if the target stops, this stops too.
ConflictsMutual exclusion — starting this stops the named services.
OnFailureService to start when this one enters Failed.

How it is kept alive — supervision and health. See Keeping services running.

FieldDefaultPurpose
ErrorControlNormalNormal (stay Failed) or Critical (sync + reboot on irrecoverable failure).
RestartPolicyOnFailureNever / OnFailure / Always.
RestartMaxRetries, RestartWindow, RestartDelay5 / 120 / 1Restart budget, the window of health that resets it, and the backoff base.
HealthCheck, HealthCheckInterval, HealthCheckTimeout, HealthCheckRetries— / 30 / 5 / 3Active health-check command and its timing.
WatchdogTimeout0Expected interval between WATCHDOG=1 pings; 0 disables.

The transition phases — hooks and timeouts. See The execution environment and The service lifecycle.

FieldDefaultPurpose
ExecStartPre, ExecStartPostCommands run before the binary / after readiness.
ExecReload(SIGHUP)Reload command or signal:<NAME>.
StartTimeout, StopTimeout30 / 10Seconds for the whole start sequence / between SIGTERM and SIGKILL.

The remaining knobs — scheduling, notify, fds, metadata.

FieldDefaultPurpose
TimerPersistent, TimerJitter1 / 0Catch up missed timer runs after reboot / random delay per firing.
NotifyAccessMainWho may send sd_notify messages (only Main is supported).
FdStoreMax0Size of the per-service fd store; 0 disables it.
ServiceSecurityinheritSecurity descriptor controlling who may manage the service.
DisplayName, DescriptionHuman-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.

ClassWhen a change takes effectFields
Immutable at runtimeNext restart only.ImagePath, Type, Identity, RequiredPrivileges, ErrorControl
Apply on next startNext start or explicit graph reload — not while running.Requires, Wants, BindsTo, Conflicts, OnFailure, Conditions, Asserts
Hot-reloadedNext relevant event, no restart.ServiceSecurity (next control request)
Reloadable at runtimeNext 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.

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.

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 catalog of every registry key peinit reads, see the Registry key reference.

Edit this page